I had a doubt on how to architecture the model.
I want to give some entities the possibility to be voted, in this case, a paper. I came up with this two possibilities:
Option 1:
Link the entity as a relationship
class Vote(model.Model):
author = models.ForeignKey(User)
created = models.DateField(auto_now=True)
value = models.IntegerField(default=1)
class Paper(models.Model):
author = models.ForeignKey(User)
edition = models.ForeignKey(ConferenceEdition)
votes = models.OneToMany(Vote)
advantages:
It's easier to work with the model (ORM)
I can use this vote entity with others
I may need this information when rendering the HTML, to show which papers the user has already voted.
Desavantages:
I'm afraid the largest the database, the slower it can get.
Option 2:
Not to link the class
class Vote(model.Model):
author = models.ForeignKey(User)
created = models.DateField(auto_now=True)
value = models.IntegerField(default=1)
entity_id = models.IntegerField()
entity_type = models.CharField(max_length=255,default='Paper')
class Paper(models.Model):
author = models.ForeignKey(User)
edition = models.ForeignKey(ConferenceEdition)
num_votes = models.IntegerField(default=0)
Avantages:
It's kind of a lazy loading, I have a counter and if I need the information I can go for it.
It's faster ( I think )
Desavantages:
You must rely on a new logic to update all the new votes.
Option 3:
I'm listening
Thanks!
Django loads many to many fields only if you explicitly call them.
So in your 1st case:
paper.votes.all()
If you want to load all the votes when doing your query, you can in django 1.4 do prefetch_related
paper = Paper.objects.get(pk=1).prefetch_related('votes')
By the way, instead of .all() you can use .count(), which generates a different database query that is much faster since it only has to count values, instead of retrieve them into django/python.
There is also a third approach:
You coud have extra field in your model: votes_count, that you would update on pre_save(), and it would hold that value for you. This way you get both: you can query for all votes, but you can also just grab a number.
Related
I have a Django app where users upload photos, and leave comments under them. The data models to reflect these objects are Photo and PhotoComment respectively.
There's a third data model called PhotoThreadSubscription. Whenever a user comments under a photo, the user is subscribed to that particular thread via creating an object in PhotoThreadSubscription. This way, he/she can be apprised of comments left in the same thread by other users subsequently.
class PhotoThreadSubscription(models.Model):
viewer = models.ForeignKey(User)
viewed_at = models.DateTimeField(db_index=True)
which_photo = models.ForeignKey(Photo)
Every time a user comments under a photo, I update the viewed_at attribute of the user's PhotoThreadSubscription object for that particular photo. Any comments by other users that have a submission time of greater than viewed_at for that particular thread are therefore new.
Suppose I have a queryset of comments, all belonging to unique photos that never repeat. I want to traverse through this queryset and find the latest unseen comment.
Currently, I'm trying this in a very DB heavy way:
latest_unseen_comment = PhotoComment(id=1) #i.e. a very old comment
for comment in comments:
if comment.submitted_on > PhotoThreadSubscription.objects.get(viewer=user, which_photo_id=comment.which_photo_id).viewed_at and comment.submitted_on > latest_unseen_comment.submitted_on:
latest_unseen_comment = comment
This is obviously not a good way to do it. For one, I don't want to do DB calls in a for loop. How do I manage the above in one call? Specifically, how do I get the relevant PhotoThreadSubscription queryset in one call, and next, how do I use that to calculate the max_unseen_comment? I'm highly confused right now.
class Photo(models.Model):
owner = models.ForeignKey(User)
image_file = models.ImageField(upload_to=upload_photo_to_location, storage=OverwriteStorage())
upload_time = models.DateTimeField(auto_now_add=True, db_index=True)
latest_comment = models.ForeignKey(blank=True, null=True, on_delete=models.CASCADE)
class PhotoComment(models.Model):
which_photo = models.ForeignKey(Photo)
text = models.TextField(validators=[MaxLengthValidator(250)])
submitted_by = models.ForeignKey(User)
submitted_on = models.DateTimeField(auto_now_add=True)
Please ask for clarification if the question seemed hazy.
I think this will do it in a single query:
latest_unseen_comment = (
comments.filter(which_photo__photothreadsubscription__viewer=user,
which_photo__photothreadsubscription__viewed_at__lt=F("submitted_on"))
.order_by("-submitted_on")
.first()
)
The key here is using F expressions so that the comparison can be done with each comment's individual date, rather than using a single date hardcoded in the query. After filtering the queryset to only include the comments that are unseen, we then order_by the date of the comment and take the first one.
I am confused to take a decision whether to use ForeignKey or ManyToManyField.
Suppose I am building an application for an event which demands tickets to get access the event and delegates may get some coupon based on the category of the ticket they have taken. I might have the following classes:
class Coupon(models.Model):
name = models.CharField()
event = models.ForeignKey(Event)
created_by = models.ForeignKey(User)
expired_time = models.DateTimeField()
description = models.TextField()
created_at = models.DateTimeField()
class CouponTicketMap(models.Model):
coupon = models.ForeignKey(Coupon)
tickets = models.ManyToManyField(Ticket)
class CouponUserMap(models.Model):
coupon = models.ForeignKey(Coupon)
users = models.ManyToManyField(User)
Organizer can map coupons to one or more tickets.
Or/And he can map to some selected or random users.
(I do not need an extra field in the intermediate table that is why I did not use through here.)
I can redesign the 2nd and 3rd model as
class CouponTicketMap(models.Model):
coupon = models.ForeignKey(Coupon)
tickets = models.ForeignKey(Ticket)
class CouponUserMap(models.Model):
coupon = models.ForeignKey(Coupon)
users = models.ForeignKey(User)
I think I can achieve what I need from both design, but want get know about the consequences of both design. So which design will get more votes when considering aspects such as performance, storage, conventional style etc. Can anybody shed some light on making a decision.
Thanks
I´ll say this model due to what you say:
class CouponTicketMap(models.Model):
coupon = models.ForeignKey(Coupon)
tickets = models.ForeignKey(Ticket)
class CouponUserMap(models.Model):
coupon = models.ForeignKey(Coupon)
users = models.ManyToManyField(User)
Cuz, one coupone can have many tickets, and many users can have a related same coupon. Dont see neccesary to stick just to one parameter, when you can use them both depending of the designed needed. Hope my opinion helps.
I am new to django and not very good at building models yet. I wanted to see best way to setup a cross ref table for my project.
In my scenario I want to allow users to search parts based off either manufacturer part number or a resellers part number.
One specific manufacturer part number can be associated to many reseller part numbers but each reseller part number can only be associated to one manufacturer part number.
So for example Manuacturer A makes a widget and calls it w12345. Reseller A calls this widget aw12345, Reseller B calls it bw12345, and Reseller C calls it widgetxyz. If a user searches on anyone of those options they will be brought to the correct part which is w12345 will all its information like price, qty, picture, etc.
I set my models up below, is this the correct way to do this? Or do I need more models?
class Parts(models.Model):
sup_name = models.CharField(max_length=50)
mfr_part = models.CharField(max_length=30)
sup_part = models.CharField(max_length=30)
part_desc = models.CharField(max_length=200)
part_price = models.FloatField(max_length=6)
part_qty = models.IntegerField(max_length=6)
part_img = models.ImageField(upload_to=None, height_field=None, width_field=None, max_length=100)
url = models.URLField()
partxref = models.ForeignKey('PartXRef')
def __unicode__(self):
return self.sup_name
class PartXRef(models.Model):
supplier = models.CharField(max_length=50)
mfr_part_number = models.CharField(max_length=30)
sup_part_number = models.CharField(max_length=30)
def __unicode__(self):
return self.supplier
There are a few things wrong here. The main issue is that the ForeignKey is the wrong way round: there are many possible PartXRefs for each Part, so the FK should live on the former pointing at the latter.
The other issue is that it is a fundamental principle of database design, called normalization, that you should never repeat data; there should be one canonical place where each piece of data lives. In this case, the manufacturer number should live only in Part, and the name of the supplier and the number they've assigned to it should live only in PartXRef.
I've searched around for a while, but can't seem to find an existing question for this (although it could be an issue of not knowing terminology).
I'm new to Django, and have been attempting to take a design which should be very expandable over time, and make it work with Django's ORM. Essentially, it's a series of many-to-many relationships using a shared junction table.
The design is a generic game crafting system, which says "if you meet [require], you can create [reward] using [cost] as materials." This allows items to be sold from any number of shops using the same system, and is generic enough to support a wide range of mechanics - I've seen it used successfully in the past.
Django doesn't support multiple M2M relationships sharing the same junction table (apparently since it has no way to work out the reverse relationship), so I seem to have these options:
Let it create its own junction tables, which ends up being six or more, or
Use foreign keys to the junction table in place of a built-in MTM relationship.
The first option is a bit of a mess, since I know I'll eventually have to add additional fields into the junction tables. The second option works pretty well. Unfortunately, because there is no foreign key from the junction table BACK to each of the other tables, I'm constantly fighting the admin system to get it to do what I want.
Here are the affected models:
class Craft(models.Model):
name = models.CharField(max_length=30)
description = models.CharField(max_length=300, blank=True)
cost = models.ForeignKey('Container', related_name="craft_cost")
reward = models.ForeignKey('Container', related_name="craft_reward")
require = models.ForeignKey('Container', related_name="craft_require")
class ShopContent(models.Model):
shopId = models.ForeignKey(Shop)
cost = models.ForeignKey('Container', related_name="shop_cost")
reward = models.ForeignKey('Container', related_name="shop_reward")
require = models.ForeignKey('Container', related_name="shop_require")
description = models.CharField(max_length=300)
class Container(models.Model):
name = models.CharField(max_length=30)
class ContainerContent(models.Model):
containerId = models.ForeignKey(Container, verbose_name="Container")
itemId = models.ForeignKey(Item, verbose_name="Item")
itemMin = models.PositiveSmallIntegerField(verbose_name=u"min amount")
itemMax = models.PositiveSmallIntegerField(verbose_name=u"max amount")
weight = models.PositiveSmallIntegerField(null=True, blank=True)
optionGroup = models.PositiveSmallIntegerField(null=True, blank=True,
verbose_name=u"option group")
Is there a simpler, likely obvious way to get this working? I'm attempting to allow inline editing of ContainerContent information from each related column on the Craft edit interface.
It sounds like you have a sort of "Transaction" that has a name, description, and type, and defines a cost, reward, and requirement. You should define that as a single model, not multiple ones (ShopContent, Craft, etc.).
class Transaction(models.Model):
TYPE_CHOICES = (('Craft', 0),
('Purchase', 1),
)
name = models.CharField(max_length=30)
description = models.CharField(max_length=300, blank=True)
cost = models.ForeignKey('Container')
reward = models.ForeignKey('Container')
require = models.ForeignKey('Container')
type = models.IntegerField(choices = TYPE_CHOICES)
Now Shop etc. can have a single ManyToManyField to Transaction.
Whether or not you use this particular model, the cost, reward and require relationships should all be in one place -- as above, or in OneToOne relationships with Craft, ShopContent etc. As you guessed, you shouldn't have a whole bunch of complex Many-To-Many through tables that are all really the same.
You mention at the bottom of your post that you're
attempting to allow inline editing of ContainerContent information from each related column on the Craft edit interface.
If you're modeling several levels of relationship, and using the admin app, you'll need to either apply some sort of nested inline patch, or use some sort of linking scheme like the one I use in my recent question, How do I add a link from the Django admin page of one object to the admin page of a related object?
I am smelling something is too complicated here, but I might be wrong. As a start,
is this any better? (ContainerContent will be figured out later)
class Cost(models.Model):
name = models.CharField(max_length=30)
class Reward(models.Model):
name = models.CharField(max_length=30)
class Require(models.Model):
name = models.CharField(max_length=30)
class Craft(models.Model):
name = models.CharField(max_length=30)
description = models.CharField(max_length=300, blank=True)
cost = models.ForeignKey(Cost)
reward = models.ForeignKey(Reward)
require = models.ForeignKey(Require)
class Shop(models.Model):
name = models.CharField(max_length=30)
crafts = models.ManyToMany(Craft, blank=True)
This is more a database design question than a specific Django one.
We have a small Django app to manage an annual conference.
There are certain models that are common to each year of the conference. For example, workshops often repeat each year, and we often use the same rooms (seminar or accommodation rooms) as well.
For these models, some of their fields are common from year to year, whilst others will vary.
For example, each AccomodationRoom has a name, a building, and features which would be common from year to year. However, other things like the actual bed availability will vary from year to year.
There is a requirement to preserve the historical data from year to year, but we also want to reduce redundant duplication if possible, and save having to retype it every year (e.g. the names of the rooms, their sites, and their features. Likewise for workshops)
My initial approach was just to create an AccomodationRoom that stored the common data, then have for example a BedAvailability that stored the transient year-to-year information, and also provided the link to each year's conference. For example:
class AccommodationRoom(models.Model):
name = models.CharField(max_length=50)
site = models.ForeignKey(Site)
features = models.ManyToManyField(AccommodationFeature, null=True, blank=True)
class BedAvailability(models.Model):
number_of_single_beds = models.IntegerField()
number_of_double_beds = models.IntegerField()
conference = models.ForeignKey(Conference)
accommodation_room = models.ForeignKey(AccommodationRoom)
class Conference(models.Model):
year = models.CharField(max_length=4) # Example
However, another way would be simply to do away with the two models, and have a single AccomodationRoom model, which contained everything, link this directly to the Conference model, and then enforce uniqueness on AccomodationRoom.name and AccomodationRoom.Conference.
class AccommodationRoom(models.Model):
name = models.CharField(max_length=50)
site = models.ForeignKey(Site)
features = models.ManyToManyField(AccommodationFeature, null=True, blank=True)
conference = models.ForeignKey(Conference)
number_of_single_beds = models.IntegerField()
number_of_double_beds = models.IntegerField()
class Meta:
ordering = ['conference', 'name']
unique_together = (("name", "conference"),)
Or perhaps there's a better way of doing this that I haven't thought of? Open to suggestions here.
Cheers,
Victor
A small modification to your first solution (I think it is better solution because it is more normalized than the second one)
class AccommodationRoom(models.Model):
name = models.CharField(max_length=50)
site = models.ForeignKey(Site)
features = models.ManyToManyField(AccommodationFeature, null=True, blank=True)
bed_availability = models.ForeignKey(BedAvailability)
class BedAvailability(models.Model):
number_of_single_beds = models.IntegerField()
number_of_double_beds = models.IntegerField()
class Conference(models.Model):
year = models.CharField(max_length=4) # Example
accommodation = models.ForeignKey(AccommodationRoom)
Using the Django admin backend, you can first create BedAvailability object with the specification of beds. Then you can create AccomodationRoom object and associate BedAvailability object with it. Then you can finally create a Conference object and associate AccommodationRoom object with this.
In case the you need a new set of BedAvailability for the same AccommodationRoom for another year, you can create a new BedAvailability object with new specifications and link it with AccommodationRoom. You would not need to re-enter AccommodationRoom data for the next conference even if the BedAvailability specifications change.