I have a simple to-do list with activities that can be ordered by the user. I use the model List, with a many-to-many field to the model Activities.
Now I need a way to store the user defined ordering of the activities on the list. Should I go with an extra field in my List model to store the order of my activity primary keys like this:
class List(models.Model):
activities = models.ManyToManyField(Activity)
order = models.CommaSeperatedIntegerField(max_length=250)
Or should I go with a solution in the Activity model, like described here:
https://djangosnippets.org/snippets/998/
What method can be considered as best practice?
you can create your own ManyToMany Model defining the extra field order
https://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships
something like:
class ActivityList(models.Model):
activity = models.ForeignKey(Activity)
list = models.ForeignKey(List)
order = models.IntegerField()
class List(models.Model)
activities = models.ManyToManyField(Activity, through='ActivityList')
Now I need a way to store the user defined ordering of the activities on the list.
Specifying and order field allows you to give each activity an order.
specifying a comma seperated string, is 100% not the way to go, IN fact it is one of the biggest anti patterns in relational databases, Is storing a delimited list in a database column really that bad?
Using a through model lets you query for the order when presenting your todo list to the user
for activity in your_list.activities.all().order_by('order'):
# display activity to user
Related
I know it's possible to query a model using a reverse related field using the Django ORM. But is it possible to also get all the fields of the reverse related model for which the query matched?
For example, if we have the following models:
class Location(models.Model):
name = models.CharField(max_length=50)
class Availability(models.Model):
location = models.ForeignKey(Location, on_delete=models.CASCADE)
start_datetime = models.DateTimeField()
end_datetime = models.DateTimeField()
price = models.PositiveIntegerField()
would it be possible to find all Locations that are available in a specific timeframe AND also get the price of the Location during that availability? We are under the assumption that Availability objects that have the same location can not have overlapping start/end datetimes.
if user_start_datetime and user_end_datetime are inputted by the user, then we could possibly do something like the following:
Location.objects.filter(
availability__start_datetime__lte=start_datetime,
availability__end_datetime__gte=end_datetime)
But I'm not sure how to also get the price field for the specific availability that did result in a match for the query.
In raw SQL, the behavior I'm talking about might be achievable via something like this:
SELECT l.id, l.name, a.price
FROM Location l
INNER JOIN Availability a
ON a.location_id = l.id
WHERE /* availability is within user-inputted timeframe */
I've considered using something like prefetch_related('availability_set'), but that would just give me all the availabilities for the Location objects that matched the query. I just want the one availability that was within the timeframe that was queried, and more specifically, the price of that availability.
When you are using an ORM, in general you fetch results from one model class at a time. Since Location and Availability are separate models, you can simply do the following:
availabilities = Availability.objects.filter(
start_datetime__lte=start_datetime,
end_datetime__gte=end_datetime)
for availability in availabilities:
print(availability.location.id, availability.location.name, availability.price)
Which is an easy to read implementation.
Now, accessing Location from an Availability object (in availability.location) requires a second SQL query. You can optimise this using select_related:
This is a performance booster which results in a single more complex query but means later use of foreign-key relationships won’t require database queries.
Simply append it to your original query, i.e.:
availabilities = Availability.objects.select_related('location').filter(...
This will create an SQL join statement in the background and the Location objects will not require an extra query.
We have a limitation for order_by/distinct fields.
From the docs: "fields in order_by() must start with the fields in distinct(), in the same order"
Now here is the use case:
class Course(models.Model):
is_vip = models.BooleanField()
...
class CourseEvent(models.Model):
date = models.DateTimeField()
course = models.ForeignKey(Course)
The goal is to fetch the courses, ordered by nearest date but vip goes first.
The solution could look like this:
CourseEvent.objects.order_by('-course__is_vip', '-date',).distinct('course_id',).values_list('course')
But it causes an error since the limitation.
Yeah I understand why ordering is necessary when using distinct - we get the first row for each value of course_id so if we don't specify an order we would get some arbitrary row.
But what's the purpose of limiting order to the same field that we have distinct on?
If I change order_by to something like ('course_id', '-course__is_vip', 'date',) it would give me one row for course but the order of courses will have nothing in common with the goal.
Is there any way to bypass this limitation besides walking through the entire queryset and filtering it in a loop?
You can use a nested query using id__in. In the inner query you single out the distinct events and in the outer query you custom-order them:
CourseEvent.objects.filter(
id__in=CourseEvent.objects\
.order_by('course_id', '-date').distinct('course_id')
).order_by('-course__is_vip', '-date')
From the docs on distinct(*fields):
When you specify field names, you must provide an order_by() in the QuerySet, and the fields in order_by() must start with the fields in distinct(), in the same order.
Let's say I want to ask a User a Question: "Order the following animals from biggest to smallest". Here's a little simplified django:
class Question(models.Model):
text = models.CharField() #eg "Order the following animals..."
class Image(models.Model):
image = models.ImageField() #pictures of animals
fk_question = models.ForeignKey(Question)
Now I can assign a variable number of Images to each Question, and customize the question text. Yay.
What would be the appropriate way to record the responses? Obviously I'll need foreign keys to the User and the Question:
class Response(models.Model):
fk_user = models.ForeignKey(User)
fk_question = models.ForeignKey(Question)
But now I'm stuck. How do I elegantly record the order of the Image objects that this User specified?
Edit: I'm using Postgres 9.5
I am generally strongly opposed to storing comma separated data in a column. However this seems like an exception to the rule! May I propose CommaSeparatedIntegerField?
class CommaSeparatedIntegerField(max_length=None, **options)[source]¶
A field of integers separated by commas. As in CharField, the
max_length argument is required and the note about database
portability mentioned there should be heeded.
This is essentially a charfield, so the order that you input will be preserved in the db.
You haven't mentioned your database. If you are fortunate enough to be on Postgresql and using django 1.9 you can use the ArrayField as well.
using arrayfield would be much better because then the conversion back and forth between string and lists would not be there. The case against comma separated fields is that searching is hard and you can't easily pull the Nth element. POstgresql arrays remove the latter difficulty.
I want to create a database of dislike items, but depending on the category of item, it has different columns I'd like to show when all you're looking at is cars. In fact, I'd like the columns to be dynamic based on the category so we can easily an additional property to cars in the future, and have that column show up now too.
For example:
But when you filter on car or person, additional rows show up for filtering.
All the examples that I can find about using django models aren't giving me a very clear picture on how I might accomplish this behavior in a clean, simple web interface.
I would probably go for a model describing a "dislike criterion":
class DislikeElement(models.Model):
item = models.ForeignKey(Item) # Item is the model corresponding to your first table
field_name = models.CharField() # e.g. "Model", "Year born"...
value = models.CharField() # e.g. "Mustang", "1960"...
You would have quite a lot of flexibility in what data you can retrieve. For example, to get for a given item all the dislike elements, you would just have to do something like item.dislikeelements_set.all().
The only problem with this solution is that you would to store in value numbers, strings, dates... under the same data type. But maybe that's not an issue for you.
I've got two entities, Item and City. How can I model like a one-to-many relation so that it represents that an item can "have" many cities? I'd like to do it with ReferenceProperty but it seems that it would be a list and there is no property for lists of keys AFAIK. Can you tell me how I should model in order to represent the relation?
When creating new objects it is possible to make something like a one-to-many if you are creating new objects with referenceproperty that is a collection:
class ItemLocation(db.Model):
reference = db.ReferenceProperty(Item,
collection_name='matched_cities', verbose_name='Cities')
But this is not exactly what I want. I want a convenient modelling where I can iterate over the cities for a given item i.e.
{% for city in item.matched_cities %}
Can you tell me how I should do this?
Thank you
You can absolutely have a list of keys. Add this to your Item model:
cities = db.ListProperty(db.Key)
Then, you can retrieve those cities from the datastore with
itemsCities = ItemLocation.get(item.cities)