Django Model field : Ordered List of Foreign Keys - django

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()

Related

Is it possible to link multiple models to one fiel in django?

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!

Django - sorting object based on user defined order in template

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))

Django ManyToManyField not present in Sqlite3

I'm new to Django and I have some issues with a ManyToMany relationship.
I work on a blastn automatisation and here are my classes:
class Annotation(models.Model):
sequence = models.IntegerField()
annotation = models.TextField()
start = models.IntegerField()
end = models.IntegerField()
class Blast(models.Model):
sequence = models.ManyToManyField(Annotation, through="AnnotBlast")
expectValue = models.IntegerField()
class AnnotBlast(models.Model):
id_blast = models.ForeignKey(Blast, to_field="id")
id_annot = models.ForeignKey(Annotation, to_field="id")
class Hit(models.Model):
id_hit = models.ForeignKey(Blast, to_field="id")
length = models.IntegerField()
evalue = models.IntegerField()
start_seq = models.IntegerField()
end_seq = models.IntegerField()
In a view, I want to access to Annotation's data from the rest of the model via this many to many field and then apply filters based on a form. But when I do a syncdb , the "sequence" field of the Blast class disappear :
In Sqlite3 :
.schema myApp_blast
CREATE TABLE "myApp_blast" (
"id" integer not null primary key,
"expectValue" integer not null
);
So I can't load data in this table as I want. I don't understand why this field disappear during the syncdb. How can I do to link the first class to the others (and then be able to merge data in a template) ?
A ManyToManyField isn't itself a column in the database. It's represented only by an element in the joining table, which you have here defined explicitly as AnnotBlast (note that since you're not defining any extra fields on the relationship, you didn't actually need to define a through table - Django would have done it automatically if you hadn't).
So to add data to your models, you add data to the AnnotBlast table pointing at the relevant Blast and Annotation rows.
For a many-to-many relationship an intermediate join table is created, see documentation here: https://docs.djangoproject.com/en/1.2/ref/models/fields/#id1

Performing a Django Query on a Model, But Ending Up with a QuerySet for That Model's ManyToManyField

I have a third party Django App (Satchmo) which has a model called Product which I make extensive use of in my Django site.
I want to add the ability to search for products via color. So I have created a new model called ProductColor. This model looks roughly like this...
class ProductColor(models.Model):
products = models.ManyToManyField(Product)
r = models.IntegerField()
g = models.IntegerField()
b = models.IntegerField()
name = models.CharField(max_length=32)
When a store product's data is loaded into the site, the product's color data is used to create a ProductColor object which will point to that Product object.The plan is to allow a user to search for a product by searching a color range.
I can't seem to figure out how to put this query into a QuerySet. I can make this...
# If the color ranges look something like this...
r_range, g_range, b_range = ((3,130),(0,255),(0,255))
# Then my query looks like
colors_in_range = ProductColor.objects.select_related('products')
if r_range:
colors_in_range = colors_in_range.filter(
Q(r__gte=r_range[0])
| Q(r__lte=r_range[1])
)
if g_range:
colors_in_range = colors_in_range.filter(
Q(g__gte=g_range[0])
| Q(g__lte=g_range[1])
)
if b_range:
colors_in_range = colors_in_range.filter(
Q(b__gte=b_range[0])
| Q(b__lte=b_range[1])
)
So I end up with a QuerySet which contains all of the ProductColor objects in that color range. I could then build a list of Products by accessing the products ManyToMany attribute of each ProductColor attribute.
What I really need is a valid QuerySet of Products. This is because there is going to be other logic which is performed on these results and it needs to operate on a QuerySet object.
So my question is how can I build the QuerySet that I really want? And failing that, is there an efficient way to re-build the QuerySet (preferably without hitting the database again)?
If you want to get a Product queryset you have to filter the Product objects and filter via the reverse relation for product color:
products = Product.objects.filter(productcolor_set__r__gte=x).distinct()
You can use the range field lookup:
You can use range anywhere you can use
BETWEEN in SQL -- for dates, numbers
and even characters.
your query:
r_range, g_range, b_range = ((3,130),(0,255),(0,255))
products = Product.objects.filter(productcolor_set__r__range=r_range,
productcolor_set__g__range=g_range,
productcolor_set__b__range=b_range).distinct()

Annotate over Multi-table Inheritance in Django

I have a base LoggedEvent model and a number of subclass models like follows:
class LoggedEvent(models.Model):
user = models.ForeignKey(User, blank=True, null=True)
timestamp = models.DateTimeField(auto_now_add=True)
class AuthEvent(LoggedEvent):
good = models.BooleanField()
username = models.CharField(max_length=12)
class LDAPSearchEvent(LoggedEvent):
type = models.CharField(max_length=12)
query = models.CharField(max_length=24)
class PRISearchEvent(LoggedEvent):
type = models.CharField(max_length=12)
query = models.CharField(max_length=24)
Users generate these events as they do the related actions. I am attempting to generate a usage-report of how many of each event-type each user has caused in the last month. I am struggling with Django's ORM and while I am close I am running into a problem. Here is the query code:
def usage(request):
# Calculate date range
today = datetime.date.today()
month_start = datetime.date(year=today.year, month=today.month - 1, day=1)
month_end = datetime.date(year=today.year, month=today.month, day=1) - datetime.timedelta(days=1)
# Search for how many LDAP events were generated per user, last month
baseusage = User.objects.filter(loggedevent__timestamp__gte=month_start, loggedevent__timestamp__lte=month_end)
ldapusage = baseusage.exclude(loggedevent__ldapsearchevent__id__lt=1).annotate(count=Count('loggedevent__pk'))
authusage = baseusage.exclude(loggedevent__authevent__id__lt=1).annotate(count=Count('loggedevent__pk'))
return render_to_response('usage.html', {
'ldapusage' : ldapusage,
'authusage' : authusage,
}, context_instance=RequestContext(request))
Both ldapusage and authusage are both a list of users, each user annotated with a .count attribute which is supposed to represent how many particular events that user generated. However in both lists, the .count attributes are the same value. Infact the annotated 'count' is equal to how many events that user generated, regardless of type. So it would seem that my specific
authusage = baseusage.exclude(loggedevent__authevent__id__lt=1)
isn't excluding by subclass. I have tried id__lt=1, id__isnull=True, and others. Halp.
The key to Django model inheritance is remembering that with a non-abstract base class everything is really an instance of the base class which might happen to have some extra data strapped on the side from a separate table. This means that when you do searches on the base table you get back instances of the base class and there's no way to tell which subclass it is without doing repeated database queries on the subclass tables to see if they contain a record with a matching key ("I have an event. Does it have a record in AuthEvent? No. What about LDAP Event?…"). Among other things this means that you can't easily filter on them in normal queries on the base class without doing a join on every subclass table.
You have a couple of choices: one would simply be to do your queries on the subclass and tally the results (ldap_event_count = LDAPEvent.objects.filter(user=foo).count(), …), which might be sufficient for a single report. I usually recommend adding a content type field to the base class so you can efficiently tell which particular subclass an instance is without having to do another query:
content_type = models.ForeignKey("contenttypes.ContentType")
That allows two major improvements: the most common one is that you can deal with many Events generically without having to do something like hit the subclass-specific accessors (e.g. event.authevent or event.ldapevent) and handling DoesNotExist. In this case it would also make it trivial to rewrite your query since you could just do something like Event.objects.aggregate(Count("content_type")) to get the report values, which becomes particularly handy if your logic gets more complicated ("Event is Auth or LDAP and …").