Django Many To Many Ordering - django

I have two tables Subjectlist and Day. Subject list is m2m in Day. So my problem is I'm creating school timetable. So for each days different subjects to be shown, when i add subjects on each days the order of subject is same.
#Models.py
class SubjectList(models.Model):
subject_name = models.CharField(max_length=25)
def __str__(self):
return self.subject_name
class Day(models.Model):
day_name = models.CharField(max_length=15)
subject_name = models.ManyToManyField(SubjectList)
class_number = models.ForeignKey(AddClass, on_delete=models.CASCADE, null=True, blank=True)
start_time = models.TimeField(null=True, blank=True)
end_time = models.TimeField(null=True, blank=True)
def __str__(self):
return self.class_number.class_number
#Views.py
class TimeTableView(APIView):
def get(self, request, id):
class_number = AddClass.objects.get(id=id)
day = Day.objects.filter(class_number=class_number.id)
print(day)
serializer = DaySerializer(day, many=True)
return Response(serializer.data)
I want to do like this
Monday - English, maths, science, Social Science
Tuesady - Maths, Social Science, Englih, Math's
but i get like this
Monday - English, maths, science, Social Science
Tuesday- English, maths, science, Social Science
both are in same order even if add subjects in different order.

You can add more fields to the M2M table by declaring a model and assigning it to the M2M relation with the through parameter
class DaySubjectList(models.Model):
day = models.ForeignKey(Day, on_delete=models.CASCADE, related_name="day_subject_lists")
subject_list = models.ForeignKey(SubjectList, on_delete=models.CASCADE, related_name="day_subject_lists")
order = models.IntegerField(default=0)
class Day(models.Model):
day_name = models.CharField(max_length=15)
subject_name = models.ManyToManyField(SubjectList, through=DaySubjectList)
class_number = models.ForeignKey(AddClass, on_delete=models.CASCADE, null=True, blank=True)
start_time = models.TimeField(null=True, blank=True)
end_time = models.TimeField(null=True, blank=True)
def __str__(self):
return self.class_number.class_number
You can set the order field using the through_defaults parameter of the related manager
my_day.subject_name.add(my_subject, through_defaults={"position": my_position})
You can order using related_name
Day.objects.filter(class_number=class_number.id).prefetch_related(Prefetch("day_subject_lists", queryset=DaySubjectList.objects.order_by("position")))
Records will only be ordered if you access my_day.day_subject_lists.all() not my_day.subject_name.all()
You can store those records under another name if thats more convenient
Day.objects.filter(class_number=class_number.id).prefetch_related(Prefetch("day_subject_lists", queryset=DaySubjectList.objects.order_by("position"), to_attr="my_prefered_name"))
be aware that my_day.my_prefered_name is a list not a queryet so don't use .all() to access it.
Use you have any issue with your models referencing each other you can use string synthax
class DaySubjectList(models.Model):
day = models.ForeignKey("myapp.Day", on_delete=models.CASCADE, related_name="day_subject_lists")
subject_list = models.ForeignKey("myapp.SubjectList", on_delete=models.CASCADE, related_name="day_subject_lists")
order = models.IntegerField(default=0)

You need to specify an order for the m2m relation [Django-doc]:
class Day(models.Model):
# ...
subject_name = models.ManyToManyField(
SubjectList,
ordered=True
)
# ...
Now if you query a Day object, the related SubjectList objects will be ordered in the order you added these.
You can further alter the order with .move(...), or .reorder(...).
Note: You should rename the subject_name field to subjects, since it relates to multiple SubjectList objects. A ForeignKey or OneToOneField indeed often uses the _name suffix, since these relate to at most one other object, but a ManyToManyField does not have this limitation.
Note: You should not use ForeignKey(.., null=True)s. A foreign key represents a relation, and a relation can not be "NULL". You should make these fields not nullable, and make the related model have a nullable parent relation (with null=True), or make the field optional with blank=True.

Related

how to filter data in different models in django?

my models
class Player(TimeStampedModel):
name = models.CharField(max_length=200)
email = models.CharField(max_length=200)
email_verified = models.BooleanField(default=False, blank=True)
phone = models.CharField(max_length=200)
phone_verified = models.BooleanField(default=False, blank=True)
company_id = models.ImageField(upload_to=get_file_path_id_card, null=True,
max_length=255)
company_id_verified = models.BooleanField(default=False, blank=True)
team = models.ForeignKey(Team, related_name='player', on_delete=models.DO_NOTHING)
def __str__(self):
return self.name
this is my model , how to filter data in multiple model?
You can use a Queryset to filter by modal object's field.
You can use this to also filter relationships on models.
In your example, you can do a filter of all the Player entries that have a Character that have Weapon with strength > 10
Player.objects.filter(character__weapon__strength__gt=10)
You can also separate them out into 3 variables for readability purposes.
player_q = Player.objects.filter(character__isnull=False)
ch_q = player_q.filter(weapon__isnull=False)
wpn_dmg = ch_q.filter(strength__gt=10)
Please note that filters are lazy and thus don't return actual model instances untill they're evaluated. I think in this case gt returns an instance.
This documentation goes over all the fieldset lookups you can do with QuerySet object methods filter(), get(), and exclude()

Django Sum in Annotate

Good afternoon,
I am really struggling with getting a sum using Annotate in DJango.
I am using User object and the following models:
class Depts(models.Model):
dept_name = models.CharField(max_length=55)
dept_description = models.CharField(max_length=255)
isBranch = models.BooleanField(default=False)
def __str__(self):
return "{}".format(self.dept_name)
class UserProfile(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name='profile')
title = models.CharField(max_length=75)
dept = models.ForeignKey(Depts, on_delete=models.CASCADE, related_name="dept", null=True)
class ActivityLog(models.Model):
activity_datetime = models.DateTimeField(default=timezone.now)
user = models.ForeignKey(User, on_delete=models.CASCADE, null=True, related_name='activity_user')
activity_category = models.ForeignKey(ActivityCategory, on_delete=models.CASCADE, null=True, related_name='activity_cat')
activity_description = models.CharField(max_length=100, default="Misc Activity")
class ActivityCategory(models.Model):
activity_name = models.CharField(max_length=40)
activity_description = models.CharField(max_length=150)
pts = models.IntegerField()
def __str__(self):
return '%s' % (self.activity_name)
What I need to do is get a group of departments with aggregating the sum of the pts earned by all the users activitylogs.
So a user is part of department, they do activities, each activity is of a type activity_category and has associated points. How can I query using the ORM to get a sum of points for everyone in each department?
Thank you, I cannot seem to wrap my mind around it.
You annotate the departments with the sum:
from django.db.models import Sum
Depts.objects.annotate(
total_pts=Sum('dept__user__activity_user__activity_category__pts')
)
Note: The related_name=… parameter [Django-doc]
is the name of the relation in reverse, so from the Depts model to the UserProfile
model in this case. Therefore it (often) makes not much sense to name it the
same as the forward relation. You thus might want to consider renaming the dept relation to userprofiles.
After setting the related_name='userprofiles', the query is:
from django.db.models import Sum
Depts.objects.annotate(
total_pts=Sum('userprofiles__user__activity_user__activity_category__pts')
)

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

Setting queryset within forms.ModelChoiceField()

The queryset for the 'jurisdiction' field is set below in the initialization. The queryset is dependent on the id that is passed in, which comes from a specific link that a user clicks. As a result, I can't define a singular queryset within the forms.ModelChoiceField(), but it seems that django requires me to do this.
class TaxForm (forms.ModelForm): #Will be used for state tax and other taxes
jurisdiction = forms.ModelChoiceField(queryset=?????)
class Meta:
model = Tax
exclude = ('user', 'taxtype',)
def __init__(self, *args, **kwargs):
self.taxtype = kwargs.pop('taxtype',None)
super(TaxForm, self).__init__(*args, **kwargs)
if int(self.taxtype) == 1:
self.fields['jurisdiction'].choices = [(t.id, t) for t in State.objects.all()]
elif int(self.taxtype) == 2:
self.fields['jurisdiction'].choices = [(t.id, t) for t in Country.objects.all()]
else:
self.fields['jurisdiction'].choices = [(t.id, t) for t in State.objects.none()]
How can I indicate that I want the jurisdiction field to be a dropdown, but not specify one queryset within the forms.ModelChoiceField()? Alternatively, how can I make the queryset that is referenced in forms.ModelChoiceField() refer to the queryset that I initialize based on the taxtype?
Thanks!
Here is my tax model
class Tax(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE, default=None)
jurisdiction = models.CharField(max_length=120, null=True, blank=True)
name = models.CharField(max_length=200)
rate = models.DecimalField(max_digits=10, decimal_places=2)
basis = models.CharField(max_length=120, null=True, blank=True)
regnumber = models.CharField(max_length=200, null=True, blank=True) #tax number that will appear on customer invoice
taxtype = models.IntegerField(blank=True, null=True) # 0 is other, 1 is state, 2 is federal
def __str__(self):
return '{} - {}'.format(self.user, self.name)
As I mentioned, ModelChoiceField is not the right thing to do here. That's for allowing the user to choose from related items from a single model that will be saved into a ForeignKey. You don't have a ForeignKey, and what's more you're setting the choices attribute in your init rather than queryset. You should make it a plain ChoiceField with an empty choices parameter:
jurisdiction = forms.ChoiceField(choices=())
(For the sake of completeness: if you did need to use ModelChoiceField you can put anything you like into the queryset parameter when you're overwriting it in __init__, because it will never be evaluated. But managers have a none method which returns an empty queryset, so you could do queryset=State.objects.none().)

Django Nested Form - Always Showing Object instead of model details

I'm working on a Django project generated via Mezzanine. I've been able to create my models, however I want to have a form where an admin can select from a list to assign a value in a many to many or a one to many relationship. For example, I have a model for Schemas:
class Schema(AutoCreatedUpdatedMixin, SoftDeleteMixin):
"""List of all Schemas in a given database"""
name = models.CharField(max_length=128, null=False)
status = models.BooleanField(max_length=128, null=False, default=True, verbose_name="Is Active")
description = models.CharField(max_length=65535, null=True, blank=True, default=None)
database = models.ForeignKey(Database, on_delete=models.CASCADE)
pull_requests = models.ManyToManyField(Link)
questions = models.ManyToManyField(Question, blank=True)
comments = models.ManyToManyField(Comment, blank=True)
technical_owners = models.ManyToManyField(Employee, related_name='technical_owners_schemas', blank=True)
business_owners = models.ManyToManyField(Employee, related_name='business_owners_schemas', blank=True)
watchers = models.ManyToManyField(Employee, related_name='watchers_schemas', blank=True)
def __unicode__(self):
return "{}".format(self.name)
And I have a model for Employees
class Employee(AutoCreatedUpdatedMixin, SoftDeleteMixin):
"""List of people with any involvement in tables or fields: business or technical owners, developers, etc"""
name = models.CharField(max_length=256, blank=False, null=False, default=None, unique=True)
email = models.EmailField(blank=True, null=True, unique=True)
def __unicode__(self):
return "{}".format(self.employee)
An employee can own multiple schemas and a schema can be owned by multiple employees. My database has an active employee in it, however when I try to create a Schema the employee shows up as Employee Object. Rather I would want the form to show the Employee.name. How can I do this? My admin file contains the following:
class SchemasAdmin(admin.ModelAdmin):
list_display = ['name', 'status', 'database', 'description']
ordering = ['status', 'database', 'name']
actions = []
exclude = ('created_at', 'updated_at', 'deleted_at')
First of all are you using python 2 or 3? For 3, the __str__ method should be used instead of __unicode__. I am writing this because it seems that there's a problem with the __unicode__ method of Employee, which although is defined as:
def __unicode__(self):
return "{}".format(self.employee)
th Employee class does not have an employee attribute (unless there's such an attribute in the mixins that class inherits from (AutoCreatedUpdatedMixin, SoftDeleteMixin) but I don't think that is the case.
In any case, the problem is that you haven't defined a propery __str__ (if using python 3) or __unicode__ (for python 2) method on the Employee class - just define it like:
return self.name
and you should see the employee's name in the django admin select fields.