1

I have to deal with a model for time periods, and those periods are different per country and for which some periods are BC and some are AD. So I added a model Period with a name and a start and end as integer fields. As dates won't do for 14000BC, I figured I would define negative years as BC and the positive as AD. Fields' start and end can be empty as the first period (name='First period', start=null, end=-14000) would read something like: "First period (before 14000 BC)" and the last period (name="Last period", start=1989, end=null) would read: "Last period (1989 - present)"

With my limited knowledge, I tried modifying the __str__ method of my model, first attempting to convert negative years to positive years and add the BC in the name.

The second issue I have is that without the conditional trials, the None fields are shown as None even though I have this in admin.py. It works for the list_display but not for the name returned by __str__.

@admin.register(Period)
class PeriodAdmin(admin.ModelAdmin):
    list_display = ('name', 'country', 'start', 'end')
    empty_value_display = '' # seems not to work on def __str__(self):!

Here is my model in models.py and this throws an error in my console:

class Period(models.Model):
    country = models.ForeignKey('Country', on_delete=models.CASCADE, null=True)
    name = models.CharField(max_length=100, blank=True, null=True)
    ## use negative values for before start and end dates prior to BC and positive for AD
    start = models.IntegerField(blank=True, null=True)
    end = models.IntegerField(blank=True, null=True)

    def __str__(self):
        if self.start < 0:
            A = f'{abs(self.start)} BC'
        elif self.start == None:
            A = ''
        else:
            A = self.start
        if self.end < 0:
            B = f'{abs(self.end)} BC'
        elif self.end == None:
            B = ''
        else:
            B = self.end
        return f'{self.name} ({A} - {B})'

So what I'm trying to achieve is:

name: Meiji
start: 1868
end: 1912

Becomes in the admin: Meiji (1868-1912)

I can get this result using:

def __str__(self):
    """String for representing the Model object."""
    return f'{self.name} ({self.start} - {self.end})'

However, consider this period with no end date:

name: Heisei
start: 1989
end: null

That would become: Heisei (1989-None) despite the empty_value_display = '', it seems not to work in __str__!

I want here: Heisei (1989 - present)

Even worse, periods with no start date:

name: Paleolithic
start: null
end: -14000

will become Paleolithic (None--14000), but I want: Paleolithic (before 14000 BC)

I tried to use some logic to achieve this:

def __str__(self):
    if self.start < 0:
        A = f'{abs(self.start)} BC'
    elif self.start == None:
        A = ''
    else:
        A = self.start
    if self.end < 0:
        B = f'{abs(self.end)} BC'
    elif self.end == None:
        B = ''
    else:
        B = self.end
    return f'{self.name} ({A} - {B})'

But that seems not to work and gives an error in the runserver console:

Error screen

Mihai Chelaru
  • 7,614
  • 14
  • 45
  • 51
Merijn van Tilborg
  • 5,452
  • 1
  • 7
  • 22
  • When you say it doesn't work, what is the value you are passing in and what does it display? Does it throw an exception, or does it simply display the wrong date? You should be clear about what the input is and what the desired output would be, and what output you're currently getting. – Mihai Chelaru Oct 07 '18 at 18:59
  • Thank you for guiding me in getting my questions phrased better. With the code above I get an error in the console File "", line 219, in _call_with_frames_removed File "", line 1 (abs(self.start) BC) ^ SyntaxError: invalid syntax – Merijn van Tilborg Oct 07 '18 at 19:06
  • That seems strange because that format string doesn't give me any syntax errors. Can you [edit your question](https://stackoverflow.com/posts/52691409/edit) to include the full traceback you're getting when you try to access the page? You can add it below your code using code formatting. – Mihai Chelaru Oct 07 '18 at 19:20
  • 1
    I editted it now to illustrate the problem better – Merijn van Tilborg Oct 07 '18 at 19:25

1 Answers1

1

Given that changing __str__() gave you the desired result in the simple case where both start and end are defined and are AD dates, I've extended your __str__() method to now give the results you wanted for the other edge cases you outlined:

def __str__(self):
    if self.start is None:
        start = 'before'
    elif self.start < 0:
        start = f'{-self.start} BC -'
    else:
        start = f'{self.start} -'
    if self.end is None:
        end = 'present'
    elif self.end < 0:
        end = f'{-self.end} BC'
    else:
        end = f'{self.end}'
    return f'{self.name} ({start} {end})'

Testing it with the following input values for name, start, and end gives the following output:

'Heisei', 1989, None
'Meiji', 1868, 1912
'Paleolithic', None, -14000

Heisei (1989 - present)
Meiji (1868 - 1912)
Paleolithic (before 14000 BC)

As for empty_value_display, I think it's meant to display entire empty fields or model instances within the Django admin page. Since you're looking to display a pretty custom result for each period, I think overriding the __str__() method as above should do just fine.

Mihai Chelaru
  • 7,614
  • 14
  • 45
  • 51
  • Thank you so much, I probably failed over if self.start is None: versus == None as Python is rather new to me too, however your working fix is neater as it gave me the complete working outcome, very much appreciated as well as your comments about how to phrase questions here with all info that is needed. I learned a lot, thanks! – Merijn van Tilborg Oct 07 '18 at 20:05
  • You can look at [this post](https://stackoverflow.com/questions/14247373/python-none-comparison-should-i-use-is-or) for an explanation of `==` versus `is` for checking for None. I'm not exactly sure what the syntax error is because the code you posted seems like it should work, but I'm glad I could help! – Mihai Chelaru Oct 07 '18 at 20:13