Suppose you have a many-to-many relation in a Django model, such as:
class GroceryList(models.Model):
items = models.ManyToManyField(GroceryItem, related_name='in_lists')
class GroceryItem(models.Model):
name = models.CharField(unique=True)
You and me can both have the same items in two different lists, such as Avocado, and they will point to the same Avocado object.
What is the best way to implement an arbitrary order for items in each list, that can be edited separately for each list? (i.e. I have Avocado first in my list, while you have it at index 4)
django-ordered-model seems like an interesting solution, but it assumes global order across all objects.
You can use the intermediate table using through and add the ordered field in that table.
class GroceryList(models.Model):
items = models.ManyToManyField(GroceryItem, related_name='in_lists',
through='Order')
class GroceryItem(models.Model):
name = models.CharField(unique=True)
class Order(models.Model):
number = models.PositiveIntegerField()
gl = models.ForeignKey(GroceryList)
gi = models.ForeignKey(GroceryItem)
So instead of doing grocerylist.items.add(groceryitem) you can do
#for groceryitem1 as 1st item in grocerylist1
Order.objects.create(gl=grocerylist1, gi=groceryitem1, number=1)
#for groceryitem1 as 10th item in grocerylist2
Order.objects.create(gl=grocerylist2, gi=groceryitem1, number=10)
Related
Let's say I have these models:
class Material(models.Model):
name = models.CharField([...])
class Consumable(models.Model):
name = models.CharField([...])
restores = models.IntegerField([...])
class Weapon(models.Model):
name = models.CharField([...])
damage = models.IntegerField([...])
# And then I have an 'inventory', like this one:
class Inventory(models.Model):
user = models.ForeignKey([...]) # to which user you want to link the item
item = models.ForeignKey([...]]) # which item
quantity = models.IntegerField([...]) # how many of it
I want to be able to have all Material, Consumable, and Weapon models listed in the 'item' field, so when you want to add an item as an inline, you would see all 3 models' objects.
Something like
# instead of this
item = models.ForeignKey(Consumable) # which item
# want something like this
item = models.ForeignKey(Consumable and Material and Weapon) # which item
# this wouldn't work ofc...
Is there a way to collect all 3 of them and pass them to the 'item' field, without the need of restarting the server? (when making a "choices" list that queries from a model you must restart the server to see the newly added objects, I don't want that.)
I also want to stick to the built-in admin of Django since it provided everything I need for the past couple of months, but I am open to any ideas.
I could be wrong but I think you are making this more complex than it needs to be. Instead of doing separate classes for materials (type of material) and consumable (type of product), you can have that built in the last class as model field as category or bolean fields.
class Products(models.Model):
material_type =
consumable = boolean for yes no or you can do multiple choice field
Then for items you can query the number of items based on material_type or consumable model fields (see query filters for for more).
all_items = Products.model.all()
consumable_items = Products.model.filter(your filter logic goes here)
Hope this helps!
These are my models:
from django.db import models
class A(models.Model):
# fields
class B(models.Model):
a = models.ForeignKey(A)
# fields
I have some items from model A:
items = A.objects.filter(some_column=some_value)
Now I want 2 model B objects for each object in items. If there are 5 objects in items then I want total 10 objects from model B, 2 model B objects for each object of model A. Hope I made my requirement clear. I tried some queries, but ended up with querying model B for each model A object.
Also the solution should be well optimized, I would like to avoid 20 different queries for 20 objects in items.
If it is not possible with ORM, then I can use raw query as well.
you can get those using related query and prefetch_related
like
items = A.objects.prefetch_related('b_set').filter(some_column=some_value)
for item in items:
/* Here you get all modal B object for particular item */
obj_of_modal_B = item.b_set.all() # Here b is model name in small
you can also overwrite related_query name using related_name
class A(models.Model):
# fields
class B(models.Model):
a = models.ForeignKey(A,related_name='custom_name')
# fields
and then use like
items = A.objects.prefetch_related('custom_name').filter(some_column=some_value)
for item in items:
/* Here you get all modal B object for particular item */
obj_of_modal_B = item.custom_name.all()
Use prefecth_related. It won't query in for loop. It will have two query only
a = A.objects.prefetch_related('b')
Read about prefetch_related in docs for more detailed information
https://docs.djangoproject.com/en/3.0/topics/db/queries/
I have a Route model which should store an ordered list of stops along that route. How should I go about modeling this relation?
class Stop(models.Model):
name = ..
latitude = ..
longitude = ..
class Route(models.Model):
stops_list = # Ordered list of stops on the route
Since there are many Stops along a Route, and stops could belong to multiple routes, I would use a ManyToMany to store this relationship. You may specify a through model to store data about the relationship, such as what time the route is expected to arrive at this stop. There are many options to add order information. One naive way would be to have an Integer order field as below, or you could store order implicity via arrival_time. If these routes do not change often, IntegerField is not a terrible implementation. However, if they do change often then you would need to update the fields.... not ideal.
class Stop(models.Model):
name = ..
latitude = ..
longitude = ..
class Route(models.Model):
stops_list = models.ManytoManyField(Stop, through='StopInfo') # Ordered list of stops on the route
class StopInfo(models.Model):
""" Model for storing data about the Stop/Route relationship """
stop = models.ForeignKey(Stop)
route = models.ForeignKey(Route)
arrival_time = models.DateTimeField(auto_now_add=True)
order = models.PositiveIntegerField()
I want the user to be able to order a list of objects in a table using javascript. Then, in a django function I would like to sort those object based on the same ordering, not on an attribute.
Is it possible? I was thinking about passing a list of pk from the template to the view and then ordering the objects according to this list, but I have not found a way to do it yet.
I don't think this is possible with queryset. Try following:
pk_list = [2, 1, 3, 4]
pk2obj = {obj.pk: obj for obj in Model.objects.all()}
objects_ordered = [pk2obj[pk] for pk in pk_list]
pkg2obj is mapping between pk and model instance object. To make a dictionary I used dictionary comprehension.
If you want to omit deleted objects:
objects_ordered = [pk2obj[pk] for pk in pk_list if pk in pk2obj]
Else if you want to replace deleted objects with default value (None in following code):
objects_ordered = [pk2obj.get(pk, None) for pk in pk_list]
I've had to solve this exact problem before.
If you want the user to be able to reorder them into a user-defined order, you can easily define a field to store this order.
As you say, initially, you could serve them in order according to id or an upload_date DateTimeField. But you could also have an PositiveIntegerField in the model, named position or order, to represent the user-defined order.
class MediaItem(models.Model):
user = models.ForeignKey(User)
upload_date = models.DateTimeField(auto_now_add = True)
position = models.PositiveIntegerField()
Whenever a user changes the order on the frontend, the JS can send the new order as an array of objects (ie. new_order = [{"pk":3, "position":1}, {"pk":1, "position":2}, {"pk":2, "position":3}]). The view can look up each instance by pk, and change the position:
for obj in new_order:
media_item = MediaItem.objects.get(pk=obj['pk'])
media_item.position = obj['position']
media_item.save()
Then always query using
objects_ordered.objects.order_by('position')
That's how we managed to do it. If you have more specific questions regarding this approach, feel free to ask in the comments.
Edit:
If the same object can be a member of many different groups or lists, and you want to store the position of the membership within that list, you can achieve this using a through model. A through model is useful when you need to store data that relates to the relationship between two objects that are related. In addition to the MediaItem class shown above, this is what your other models would look like:
class Album(models.Model):
media_items = models.ManyToManyField(MediaItem,
related_name = 'album_media_items',
through = 'Membership')
class Membership(models.Model):
album = models.ForeignKey(Album,
related_name = 'album')
media_item = models.ForeignKey(MediaItem,
related_name = 'media_item')
date = models.DateTimeField(auto_now_add = True)
position = models.PositiveIntegerField()
Then, you could query the Membership instances, instead of the MediaItem instances.
# get id of list, or album...
alb = Album.objects.get(pk=id_of_album)
media_items = Membership.objects.filter(album=alb).order_by('position')
for item in media_items:
# return the items, or do whatever...
# keep in mind they are now in the user-defined order
You can do this:
pk_list = [1,5,3,9]
foo = Foo.objects.filter(id__in=pk_list)
#Order your QuerySet in base of your pk_list using Lambda
order_foo = sorted(foo, key = lambda:x , pk_list.index(x.pk))
How can I prefetch_related objects in Django and order them by a field in an intermediary table?
Here's the models I'm working with:
class Node(models.Model):
name = models.CharField(max_length=255)
edges = models.ManyToManyField('self', through='Edge', symmetrical=False)
class Edge(models.Model):
from_node = models.ForeignKey(Node, related_name='from_node')
to_node = models.ForeignKey(Node, related_name='to_node')
weight = models.FloatField(default=0)
Given a node, I'd like to prefetch all of the related nodes, ordered by weight.
When I use this query:
n = Node.objects.prefetch_related('to_node').order_by('edge__weight').get(name='x')
the order_by has no effect.
Edit:
My best answer so far
n = Node.objects.get(name='x')
edges = Edge.objects.filter(from_node=n).prefetch_related('to_node').order_by('weight')
Then instead of iterating n.edges (as I'd prefer), I iterate edges.to_node
Nowadays, you can also use the Prefetch class to achieve this:
https://docs.djangoproject.com/en/1.10/ref/models/querysets/#django.db.models.Prefetch
Or, if you want to do this all the time as a default, you can look into the meta ordering on the intermediary table, something like:
class SomeThroughModel(models.Model):
order = models.IntegerField("Order", default=0, blank=False, null=False)
...
class Meta:
ordering = ['order'] # order is the field holding the order
Just a conceptual idea (written from memory).
The problem is, that the order_by refers to the Node model.
However, there is a way to
Node.objects.get(name='x').edges.extra(select={'weight':'%s.weight' % Edge._meta.db_table}).order_by('weight')
This will force the ORM to:
Add 'weight' field, which would normally be omitted.
Order the results by it.
Number of queries should be the same as if the prefetch_query worked, one to get the Node, second to get the related nodes.
Unfortunately this is not a very 'clean' solution, as we need to use _meta.
Not that clean though..
//Untested Code
Node n = Node.objects.get(name="x")
//This would return To Node IDs' ordered by weight
n.edges.filter(from_node = n).values_list('to_node', flat=True).order_by('weight')