Django - Days of the week (int) isn't rendering a string - django

I have a model in my app that stores the a single day of the week as:
DAYS = (
(0, 'Monday'),
(1, 'Tuesday'),
(2, 'Wednesday'),
(3, 'Thursday'),
(4, 'Friday')
)
day = models.IntegerField(validators=[MaxValueValidator(4), MinValueValidator(0)], blank=True, choices = DAYS)
According to the Django Documentation,:
The first element in each tuple is the actual value to be set on the model, and the second element is the human-readable name
However, that doesn't seem to be the case for me. For instance, in this template here in the same app, when I have something like:
{{q.day}}
This gives me: 1 instead of Tuesday.
I tried some suggestions from SO, including creating your own model, and even considered passing custom functions through Jinja2, though I feel this to be unnecessarily complex. What would be the best way to go about this. Do I not understand the functionality of this clearly?
Note: I want to store the day to be int because my app is running some complex algorithms and I want to just convert it for display purposes.

Try:
{{ q.get_day_display }}
(without parenthesis in the template)
https://docs.djangoproject.com/fr/3.1/ref/models/instances/#django.db.models.Model.get_FOO_display

When you have a choice in you model field you must use {{object.get_attribut_display}} not {{object.attribut}}.
Don't forget to read documentation carefully.

Related

How to store multiple values on a django model field

I have a model where I store the recurring sessions on my gym.
DAYS_OF_WEEK = (
(0, _("Monday")),
(1, _("Tuesday")),
(2, _("Wednesday")),
(3, _("Thursday")),
(4, _("Friday")),
(5, _("Saturday")),
(6, _("Sunday")),
)
class RecurringSession(models.Model):
session = models.ForeignKey(SessionType, db_index=True, on_delete=models.CASCADE)
dayofweek = models.PositiveSmallIntegerField(choices=DAYS_OF_WEEK)
time = models.TimeField()
However some classes happen more than once a week. Whats the best way to store multiple values (between 1 to 7) on that modelfield?
I'm using Django Mysql 8.0, and from what I understand it supports JSON field but from the django docs it seems that is only supported by Postgres so far.
Is there a specific django way to do this? Maybe storing a charfield with comma-separated values?
I wont need to search by that field.
Every week, there will be a cronjob that will read this model and create an instance of each session on another model.
(I need each session as an instance for customization: teacher substitution, session roster, session status - i may cancel a single session on a bank holiday or for another reason).
So this "RecurringSessions" will be where I store the session information that will be generated every week.
The way that I wrote at first there will be an model instance for each day of the week of the same session, but I want to group them in the same instance because I think its looks nicer and may make it easier to set the teacher in bulk.
Alternatively, I could store a json with a dict for multiple day/time values for each session, however that would make it a little bit more difficult to make sure that if the cronjob is run twice the class doesnt get duplicated.
Now, to avoid that, I have a UniqueConstraint in my schedule model with fields "day", "time" and "room"
Using Django 3.0.6, Python 3.8.2, and MySQL 8.0.20.
thanks
You're attempting to do a many-to-one relationship, so you'll either have to use this method docs.djangoproject.com/en/3.0/topics/db/examples/many_to_one, or if you want to avoid creating a database table and utilize Django's choice field, you can use this extension.
Since there are seven fixed days in a week, I'll prefer the second method instead of creating a DB table, and unnecessary DB queries.
It's hard to say the best structure without knowing what you're ultimately going to do with the model, however, I don't see why you couldn't have a single Session model that covers everything - like so:
class Session(models.Model):
TYPE_CHOICES = (('C','Cardio'), #...and so on
DAY_CHOICES = ((1,'Monday'), #...and so forth
type = models.CharField(max_length=10, choices=TYPE_CHOICES)
day = models.PositiveSmallIntegerField(choices=DAY_CHOICES)
time = models.TimeField()
You could find the recurring sessions by counting the occurrences of a session once you've applied a filter to type or another field. That would keep your model structure concise and give you enough information in a single place to do filtering, annotation, etc. as required.

How can I search the choice field using display name?

I want to query the item from the database using django SmallIntegerField. The code I used is
Here is the class defination:
class Equipment(models.Model):
asset_status = (
(0, 'online'),
(1, 'offline'),
(2, 'unknown'),
(3, 'wrong'),
(4, 'x'),
)
status = models.SmallIntegerField(
choices=asset_status, default=0, verbose_name='Device Status')
The query code I used is
def filter_queryset(self, qs):
sSearch = self.request.GET.get('search[value]', None)
print(sSearch)
if sSearch:
qs = qs.filter(Q(status__icontains=sSearch))
return qs
I want to query this column by 'online', 'offline' and so on. Do you know how to do this?
The reference I have searched are
Search choice field by the name in django - python
I also see the Choices API in
https://django-model-utils.readthedocs.io/en/latest/utilities.html#choices
But there is no Q. I am not sure whether it works. Could you please tell me about this?
And I searched for a method called get_FOO_display, but I don't think it can be solved. Neither did I think it can be combined with Q.
Thanks
Choices follow the format of a actual_value_for_db, human_readable_name tuple, where the human_readable_name is only for display purposes. That is, you cannot query for it as only the first item of the tuple is stored in the database.
If you want to make it a bit easier to find out what value you need to use you can use an enum-like declaration of the choices:
class Equipment(models.Model):
ONLINE = 0
OFFLINE = 1
UNKNOWN = 2
WRONG = 3
X = 4
asset_status = (
(ONLINE, 'online'),
(OFFLINE, 'offline'),
(UNKNOWN, 'unknown'),
(WRONG, 'wrong'),
(X, 'x'),
)
Then you can query for a choice by using a 'name' of the member of the Equipment model: Equipment.objects.filter(status=Equipment.OFFLINE)
It is not clear what exactly the value in your request is and where it comes from. I would suggest just using a ModelForm to ask the user for the right status type. A ModelForm should take care of the choices for that formfield and return the right type for the selected choice (which should be an integer, not a string!). Use a generic FormView and the rest should be a piece of cake.
status is a SmallIntegerField so, what you have in the database are integers. When you provide code like this:
asset_status = (
(0, 'online'),
(1, 'offline'),
(2, 'unknown'),
(3, 'wrong'),
(4, 'x'),
)
you're telling Django: "hey, I want to be able to display the 0 option value as 'online' , 1 as 'offline' ..." and so on.
So, you Django don't write such values ('online', 'offline', ...) to the database hence you can't query on them.
But ...
I suppose you are having trouble getting the right integer value for the query after the user selects one of the options for filtering, you should take a look at ChoiceField, perhaps you can use it for your filter form.

django ORM - order by status value

I have a model with three statuses: draft, launching, launched. What I want is to display models in a particular order: first drafts, then launching ones, and then launched ones. I don't want to name my attributes as 0_draft, 1_launching, 2_launched as this will be a problem down the road; i need clear values in my DB.
Is there a way to annotate these values with some integers?
The ideal syntax for this would look like this:
def detect_status_number(campaign):
if campaign.status == 'draft':
return 1
... etc ...
cs = Campaign.objects.annotate(new_arg=lambda campaign: detect_status_number(campaign))
cs = cs.order_by('new_arg').all()
this obviously doesn't work; but is there a way to make this work?
I think this will work
cs = Campaign.objects.extra(
select={"weight_status":"case when campaign.status='draft' then 0 when campain.status = 'launching' then 1 else 2 end"}
).order_by('weight_status')
Although you could certainly make your approach work, let me recommend another approach: using the choices field and "enumeration". Here is an example:
STATUS_TYPE = (
(1, "draft"),
(2, "launching"),
(3, "launched"),
)
And then for your model field:
status = models.IntegerField(choices=STATUS_TYPE)
I realize that this may not be an acceptable answer if your model design is already set, but this is a good design pattern to know.

Add an IntegerField to a Datefield in a Django template

I'm trying to display the expiry date of a bonus from within a Django template. At the moment the opening_date is stored as a datefield and we store the bonus term as an integerfield. Unfortunately just trying to add the bonus term to the opening date fails and the furthest I have got so far is:
{{product_form.instance.opening_date|add:product_form.instance.bonus_term}}
I have tried just adding it to the month but unfortunately I need the whole date returned to display.
For a better idea of what I want is say the opening date was 01/01/2012 and the bonus term was 12, I want to display the expiry date of 01/01/2013. I realise this is probably better off being in the database but due to the way it has been previously set up there is a large amount of existing data that wouldn't have it.
Thanks.
I think that, for your scenario, the most elegant solution is to create a model method in your model that calcule expire date, then call the method in template:
In model:
class product(models.Model):
opening_date = ...
bonus_term = ...
def expire_date( self ):
return self.opening_date + timedelta( days = self.bonus_term )
In template:
{{product_form.instance.expire_date}}
I'm sure that you will call this method in more other lines of your code.

In Django, how can I order by a multiple-choice CharField in arbitrary not alphabetical order?

Imagine a model Shirts with a size CharField, with values limited to a small number of choices, e.g. 'small', 'medium', 'large', 'xlarge' etc.
To get the shirts grouped by size, you'd do:
Shirts.objects.order_by('size')
But Django will (naturally) order the groups alphabetically, i.e. 'large' then 'medium' then 'small' then 'xlarge'. What I want is to have 'small' before 'medium' before 'large' etc.
I.e. what I naturally want to do is something like the following pseudocode:
size_order = {'small': 1, 'medium': 2, 'large': 3, 'xlarge': 4}
Shirts.objects.order_by('size_order[size]')
What's the best way to accomplish this?
EDIT: See my comments to answers below for thoughts on various suggested approaches. I've stumbled on a custom Manager/QuerySet approach using the SQL ORDER BY CASE syntax which I'm investigating.
I figured out the closest thing to what I'm looking for, which is to use QuerySet.extra() method to take advantage of SQL's CASE WHEN/THEN syntax, which Django doesn't support directly:
CASE_SQL = '(case when size="small" then 1 when size="medium" then 2 when size="large" then 3 when size="xlarge" then 4 end)'
Shirt.objects.extra(select={'shirt_order': CASE_SQL}, order_by=['shirt_order'])
This may well seem overkill and/or mucky given my (artificial) example, but it's the trick I was looking for! Thanks to everyone for the other perfectly valid approaches to this problem, which somehow indirectly sparked me to figure out this approach.
P.S. It's tempting to create a custom model Manager/QuerySet combo that provides a more native Django-interface for this sort of custom ordering via SQL's CASE WHEN/THEN syntax, but I'll leave that as a homework assignment for myself for another time!
NOTE: The syntax for the CASE WHEN/THEN is database-specific. The syntax above is for SQLite. For PostgreSQL, omit the parentheses and use escaped single quotes instead of double quotes.
It sounds like you don't want to hard-code the possible choices (because you used a charfield), but at the same time you say there are a small number of choices.
If you are content to hard-code the choices then you could change to an integerfield instead:
class Shirt(models.Model):
SIZE_CHOICES = (
(1, u'small'),
(2, u'medium'),
(3, u'large'),
(4, u'x-large'),
(5, u'xx-large'),
)
size = models.IntegerField(choices = SIZE_CHOICES)
If you don't want to hard-code the size choices then you probably want to move the available sizes out to a separate model and reference it as a foreignkey from your Shirt model. To make it arbitrarily sortable you would need an index of some sort other than the primary key that you can sort on. Maybe something like this:
class Size(models.Model):
sortorder = models.IntegerField()
name = models.CharField()
class Meta:
ordering = ['sortorder']
You should set up your size field with choice tuples ordered the way you want them. In your models.py you'd have something like:
from django.db import models
SHIRT_SIZE_CHOICES = (
(u"0", u"small"),
(u"1", u"medium"),
(u"2", u"large"),
(u"3", u"xlarge"))
class Shirt(models.Model):
...
size = models.CharField(max_length=2, choices=SHIRT_SIZE_CHOICES)
Then order_by will sort them as you intend.
Without writing a custom sorting function, just tack on an order field to the model.
class Shirt(models.Model):
...
order = models.IntegerField(default=0)
class Meta:
ordering = ('order',)
Shirt.objects.filter(name='Awesome Shirt')
Or, more appropriately, create a new model called Size.
If you don't want to store the field values as integers, then the built in order_by() method won't be able to handle your custom case. You'll have to create a function of your own to sort the data once you've retrieved it.
And to do that, of course the best way would be to map your arbitrary values to integers in respective order and then sort by that arrangement :).