Many to Many in two directions - django

I have a friend that requested something that I was thinking would be simple and quick. It never turns out that way. Quick disclaimer, model design is a krux of mine. I often spend too long perfecting it only to have to rework it several times. Anyway, here is the current state of my model. For everything, it works, except when creating 'raids'.
from django.db import models
# Create your models here.
class PlayerRole(models.Model):
"""
PlayerRole Model
"""
role = models.CharField(max_length=20)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.role
# this is a inner class which is used to define unique index columns. You can specify multiple columns in a list or tuple.
class Meta:
unique_together = ['role']
class PlayerClass(models.Model):
"""
PlayerClass Model
"""
name = models.CharField(max_length=100)
color = models.CharField(max_length=6)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
# this is a inner class which is used to define unique index columns. You can specify multiple columns in a list or tuple.
class Meta:
unique_together = ['name']
class Player(models.Model):
"""
Player Model
"""
name = models.CharField(max_length=100)
playerclass = models.ForeignKey(PlayerClass, on_delete=models.CASCADE, blank=True, null=True)
playerrole = models.ForeignKey(PlayerRole, on_delete=models.CASCADE, blank=True, null=True)
value = models.IntegerField(default=0)
reliability = models.IntegerField(default=0)
last_drop = models.DateField(auto_now=False, blank=True, null=True)
last_raid_attended = models.DateField(auto_now=False, blank=True, null=True)
last_boss_attended = models.DateField(auto_now=False, blank=True, null=True)
drop_received = models.BooleanField(default=False)
note = models.TextField(null=True, blank=True)
core_raider = models.BooleanField(default=False)
enabled = models.BooleanField(default=True)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
# this is a inner class which is used to define unique index columns. You can specify multiple columns in a list or tuple.
class Meta:
unique_together = ['name']
class WoWInstance(models.Model):
"""
Instance Model
"""
name = models.CharField(max_length=100)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
# this is a inner class which is used to define unique index columns. You can specify multiple columns in a list or tuple.
class Meta:
unique_together = ['name']
class Boss(models.Model):
"""
Boss Model
"""
name = models.CharField(max_length=100)
instance = models.ForeignKey(WoWInstance, on_delete=models.CASCADE)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return self.name
# this is a inner class which is used to define unique index columns. You can specify multiple columns in a list or tuple.
class Meta:
unique_together = ['name']
class Raid(models.Model):
"""
Raid Model
"""
date = models.DateTimeField(auto_now_add=True)
boss = models.ForeignKey(Boss, on_delete=models.CASCADE, null=True, blank=True)
success = models.BooleanField()
attendees = models.ManyToManyField(Player)
# this function will be invoked when this model object is foreign key of other model(for example Employee model.).
def __str__(self):
return str(self.date) + ' + ' + self.boss.name
# this is a inner class which is used to define unique index columns. You can specify multiple columns in a list or tuple.
class Meta:
unique_together = ['boss']
The idea here is simply to track attendance on each attempt at fighting something in a game. In the most general and simple sense - Many Raids can contain many of the same Bosses. Many Raids can contain many of the same Players.
Now, I thought that because Django automatically creates ID fields, this method wouldn't be an issue. But, it seems not to be the case. If I create a raid with the same Boss more than once, I get...
Raid with this Boss already exists.
What am I missing here? Since all Raid and Boss are unique, shouldn't the two never overlap?

If I create a raid with the same Boss more than once, I get...
Raid with this Boss already exists.
Well that is due to the unique_together constraint:
class Raid(models.Model):
# ...
class Meta:
unique_together = ['boss']
If you write unique_together, it means you enforce a uniqness constraint on a combination of fields. But since you here have mentioned only one field ('boss'), you thus add a unique=True constraint on that specific field.
It thus means that no two Raids can exist with the same boss field. You probably want to remove that.
Since all Raid and Boss are unique, shouldn't the two never overlap?
The Raids and Bosses are already unique. An object does not belong to two models at once (unless one is the subclass of another, but let us ignore that case). You even made the name unique as well (although you better do that by adding a unique=True parameter to the name fields). So there is no need at all to make the 'boss' unique here, since you clearly do not want that.

Related

How to store multiple objects as a list in Django model fields? [duplicate]

I want to store some additional information in that, automatically created, ManyToMany join-table. How would I do that in Django?
In my case I have two tables: "Employees" and "Projects". What I want to store is how much each of the employees receives per hour of work in each of the projects, since those values are not the same. So, how would I do that?
What occurred to me was to, instead of the method "ManyToManyField", create explicitly a third class/table to store those new informations and to set its relationship with "Employees" and "Projects" using the "ForeignKey" method. I'm pretty sure it will work, but is this the best approach?
Here is example of what you want to achieve:
http://docs.djangoproject.com/en/dev/topics/db/models/#extra-fields-on-many-to-many-relationships
In case link ever breaks:
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self): # __unicode__ on Python 2
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # __unicode__ on Python 2
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)

How to use two unique constraints in Django model?

I have a Django model for a player of a game
class Player(models.Model):
name = models.CharField(max_length=50)
team = models.ForeignKey('Team', on_delete=models.CASCADE, blank=True, null=True)
game = models.ForeignKey('Game', on_delete=models.CASCADE)
objects = GameManager()
class Meta:
unique_together = ('name', 'game',)
I have only one unique constraint, that the name and the game are unique together.
Now, I would like to extend our page by adding registered users. So, I would add this to the model.
user = models.ForeignKey('auth.User', on_delete=models.CASCADE, blank=True, null=True)
So, an registered user can subscribe to a game by adding a name, team, game, and his/her user. However, the user should only be able to add his account once to an game, which would be a second unique constrain
unique_together = ('user', 'game',)
Is it possible to give in Django two unique constraints to the model? Or do I have to search in the table manually prior to saving the new entries? Or is there a better way?
Yes, in fact by default unique_together is a collection of collections of fields that are unique together, so something like:
class Player(models.Model):
name = models.CharField(max_length=50)
team = models.ForeignKey('Team', on_delete=models.CASCADE, blank=True, null=True)
game = models.ForeignKey('Game', on_delete=models.CASCADE)
objects = GameManager()
class Meta:
unique_together = (('name', 'game',), ('user', 'game',))
Here we thus specify that every name, game pair is unique, and every user, game pair is unique. So it is impossible to create two Player objects for the same user and game, or for the same game and name.
It is only because a single unique_together constraint is quite common, that one can also pass a single collection of field names that should be unique together, as is written in the documentation on Options.unique_together [Django-doc]:
Sets of field names that, taken together, must be unique:
unique_together = (("driver", "restaurant"),)
This is a tuple of tuples that must be unique when considered
together. It's used in the Django admin and is enforced at the
database level (i.e., the appropriate UNIQUE statements are included
in the CREATE TABLE statement).
For convenience, unique_together can be a single tuple when dealing with a single set of fields:
unique_together = ("driver", "restaurant")
You should use models.UniqueConstraint (reference).
As noted in the reference:
UniqueConstraint provides more functionality than unique_together. unique_together may be deprecated in the future.
Do this:
class Meta:
constraints = [
models.UniqueConstraint(fields=['name', 'game'], name="unique_name_game"),
models.UniqueConstraint(fields=['user', 'game'], name="unique_user_game"),
]
For example please refer to this :-
class Stores(models.Model):
name = models.CharField(max_length=50)
address = models.CharField(max_length=50)
lat = models.FloatField()
lng = models.FloatField()
merchant = models.ForeignKey(Profile, on_delete=models.CASCADE, related_name="stores")
def __str__(self):
return "{}: {}".format(self.name, self.address)
class Meta:
verbose_name_plural = 'Stores'
class Items(models.Model):
name = models.CharField(max_length=50, unique=False)
price = models.IntegerField()
description = models.TextField()
stores = models.ForeignKey(Stores, on_delete=models.CASCADE, related_name="items")
def __str__(self):
return self.name
class Meta:
verbose_name_plural = "Items"
unique_together = ('name', 'stores',)

ForeignKey to a model that is defined after/below the current model

Having the error msg
order = models.ForeignKey(Order, on_delete=models.CASCADE)
NameError: name 'Order' is not defined
So how ever I do one class will be missing cuz of the class is below the currently reading class so that the class is missing. How do I solve this? i've read about many-to-many function, might that solve the problem?
class Items(models.Model):
name = models.CharField(max_length=10)
def __str__(self):
return self.name
class OrderedItem(models.Model):
items = models.ForeignKey(Items, on_delete=models.CASCADE)
order = models.ForeignKey(Order, on_delete=models.CASCADE)
amount = models.IntegerField()
def __str__(self):
return self.items
class Order(models.Model):
#clientID
orderedItem = models.ForeignKey(OrderedItem, on_delete=models.CASCADE)
#truckId Foreign key till truck
created = models.DateTimeField(auto_now=False, auto_now_add=True)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
emergency = models.BooleanField(default=False)
status = models.IntegerField()
#building
#floor
def __str__(self):
return self.id
Use the fully-qualified model string
When this happens, I usually resort to what's called the fully-qualified model string, fancy term for what's essential a string representing the model and the containing app in the format of 'app_label.ModelName'
E.g. if your model is Order, then the model string name would be the string 'Order'
So you can already do:
order = models.ForeignKey('Order', on_delete=models.CASCADE)
With the above, Django will look for the model 'Order' in the same app. It's okay if you have not defined it yet, as long as it is defined.
If that model happens to come from a different app, it would be:
order = models.ForeignKey('appname.Order', on_delete=models.CASCADE)
Reverse query clashes
Because Order points to OrderItems and OrderItems point to Order you get a clash with related queries that Django generate for you. You can disable those with related_name='+':
order = models.ForeignKey('Order', on_delete=models.CASCADE, related_name='+')
Better modeling
Since a OrderedItem already "belongs" to an Order, there's no point in having a ForeignKey from Order to OrderedItem, you can just remove it instead of dealing with the above clash.
So what you'd have would look like this
Item
Order
OrderedItem
+ FK(Item)
+ FK(Order)
A design which wouldn't involve referencing a model that hasn't been defined yet :)
The reason it can't find the class order is because it hasn't been defined yet, you either need to specify it as a string as shown by Shang Wang, or change the order of them in your models.py
class Order(models.Model):
#clientID
orderedItem = models.ForeignKey(OrderedItem, on_delete=models.CASCADE)
#truckId Foreign key till truck
created = models.DateTimeField(auto_now=False, auto_now_add=True)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
emergency = models.BooleanField(default=False)
status = models.IntegerField()
#building
#floor
def __str__(self):
return self.id
class OrderedItem(models.Model):
items = models.ForeignKey(Items, on_delete=models.CASCADE)
order = models.ForeignKey(Order, on_delete=models.CASCADE)
amount = models.IntegerField()
def __str__(self):
return self.items
Changing the order has its advantages over specifying as a string since it will allow IDE's to find usages of the class should it ever need refactoring.
Since you have foreign keys to both classes, the above won't work and only applies to one-to-one or one-to-many relationships. Instead, it would be better to define a ManyToManyField instead of the two foreign keys
You have defined class Items before use it and you have not any error.
you must define class Order before class OrderedItem.

Order a ManyToMany relationship, Django

With these models:
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self):
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self):
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person)
group = models.ForeignKey(Group)
order_index = models.IntegerField(unique=True)
class Meta:
ordering = ['order_index']
I've been trying to get the following code to work:
group = Group.objects.get(id=1)
group.members.all() # I want the resulting membership objects to ordered by order_index as default.
Is this possible? The answers to similar questions require you to write group.members.all().order_by(...). I am trying to have it so the results are ordered by order_index by default.
This is how you get the user, and then the members associated with that user ordering them by date_joined. Not sure how you would do it automatically because the ordering is usually by the pk value which I'm assuming would correspond with the date joined because the higher the pk the later joined.
user = Person.objects.get(pk=request.user.id)
get_members = user.group.members.all().order_by('date_joined')

Model with Foreign keys not behaving as expected - Django

I have a model with two foreign keys to create many to many relationship - I am not using many to many field in Django
class Story(models.Model):
title = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def __unicode__(self):
return self.title
class Category(models.Model):
categoryText = models.CharField(max_length=50)
parentCat = models.ForeignKey('self',null=True,blank=True)
def __unicode__(self):
return self.categoryText
class StoryCat(models.Model):
story = models.ForeignKey(Poll,null=True,blank=True)
category = models.ForeignKey(Category,null=True,blank=True)
def __unicode__(self):
return self.story
I would like to query for a category like 'short', and retrieve all the unique keys to all stories returned.
>>>c=Category(categoryText='short')
>>>s=StoryCat(category=c)
when I try this I get errors "AttributeError: 'NoneType' object has no attribute 'title'. How can I do this?
I would like to query for a category like 'short', and retrieve all the unique keys to all stories returned.
c=Category.objects.get(categoryText='short')
story_ids = StoryCat.objects.filter(category=c).values_list('story')
And about your models:
Category name should probably be unique. And declare your many-to-many relation.
class Category(models.Model):
categoryText = models.CharField(max_length=50, unique=True)
stories = models.ManyToManyField(Story, through='StoryCat')
...
It makes no sense for intermediary table FK fields to be nullable. Also I assume the same story should not be added to the same category twice, so set a unique constraint.
class StoryCat(models.Model):
story = models.ForeignKey(Poll)
category = models.ForeignKey(Category)
class Meta:
unique_together = ('story', 'category')
The lines you're executing in the interpreter are not queries - they are instantiating new objects (but not saving them).
Presumably you meant this:
>>>c=Category.objects.get(categoryText='short')
>>>s=StoryCat.objects.get(category=c)