I’m developing a web application where users can subscribe to different series of texts, that would be seen inside the system. I want to create a relationship between the User and the SeriesText, so I can grab the current logged user in the VIEW, and just return all of his subscriptions at the dashboard template.
An user would be able to subscribe to as many series as he wants.
I’m using the default User model from django.contrib.auth.models, and I’m not sure how to create this relationship.
I read a lot and I think the correct usage here would be Many-to-many (is that correct?), so I tried this, using a pivot table/model called Subscriptions:
from django.contrib.auth.models import User as BaseUserClass
class User(BaseUserClass):
subscriptions = models.ManyToManyField(SeriesText, through="Subscription")
class SeriesText(models.Model):
series_name = models.CharField(max_length=255)
series_text = models.CharField(max_length=255)
subbed_users = models.ManyToManyField(User, through="Subscription")
class Subscription(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
series = models.ForeignKey(SeriesText, on_delete=models.CASCADE)
def subscribe(self, user_id, series_id):
self.user = user_id
self.series = series_id
self.save()
But that didn’t seem to work, I got errors even trying to use a User.objects.get(pk=1), don’t really know why.
I’m really confused if I need to put the relationship both ways, like created models.ManyToMany on SeriesText model, and on an extended User model (that I don’t even know if that’s really the way to do it). I'm not even sure if that the correct way to make a relationship using the default auth user model.
To be able to later also search for all users subscribed to a series, I think that the models.ManyToMany should also be on the SeriesText model, is that also correct?
Can someone help me understand if I’m using the correct relationship (many-to-many), and how to make this relationship? Thanks in advance, I’m pretty new to Django I’m hitting a wall here.
You only need to add a m2m field on SeriesText. Fundamentally, it doesn't matter which model a many-to-many field is attached to (the database will look the same and the data access will be similar if not identical). Based on my experience, it's better if you don't have to mess with the Django User model:
class SeriesText(models.Model):
series_name = models.CharField(max_length=255)
series_text = models.CharField(max_length=255)
subscribers = models.ManyToManyField(User, related_name='subscriptions')
def __str__(self):
return self.series_name
To see how that works, first add some data:
s1 = SeriesText.objects.create(series_name='name1', series_text='text1')
s2 = SeriesText.objects.create(series_name='name2', series_text='text2')
u1 = User.objects.create_user('user1')
u2 = User.objects.create_user(username='user2')
u3 = User.objects.create_user(username='user3')
u4 = User.objects.create_user(username='user4')
u5 = User.objects.create_user(username='user5')
s1.subscribers.add(u1, u2, u3)
s2.subscribers.add(u3, u4, u5)
Then to fetch all subscribers for a SeriesText (I'm fetching the objects from the database here to show that it is not an artefact of the last two lines above - data has been changed in the database):
>>> SeriesText.objects.get(series_name='name1').subscribers.all()
[<User: user1>, <User: user2>, <User: user3>]
>>> SeriesText.objects.get(series_name='name2').subscribers.all()
[<User: user3>, <User: user4>, <User: user5>]
to fetch all subscriptions for a user (we declared that the relation from User to SeriesText should be named subscriptions in the many-to-many field):
>>> User.objects.get(username='user1').subscriptions.all()
[<SeriesText: name1>]
>>> User.objects.get(username='user2').subscriptions.all()
[<SeriesText: name1>]
>>> User.objects.get(username='user3').subscriptions.all()
[<SeriesText: name1>, <SeriesText: name2>]
>>> User.objects.get(username='user4').subscriptions.all()
[<SeriesText: name2>]
>>> User.objects.get(username='user5').subscriptions.all()
[<SeriesText: name2>]
If we hadn't declared the related_name we would have had to use the default:
User.objects.get(username='user1').seriestext_set.all()
which doesn't read as well.
Related
I have a a model Meeting that is related to Signup which in turn is related Attendee.
Models:
class Meeting(models.Model):
date = models.DateTimeField(auto_now=False)
class Signup(models.Model):
meeting = models.ForeignKey(Meeting, on_delete=models.CASCADE, default=1)
...
class Attendee(models.Model, PrintNameTag):
signup = models.ForeignKey(Signup, on_delete=models.CASCADE, related_name="attendee")
attendee = models.CharField(blank=False, max_length=200)
...
How do I make a queryset on Meeting.objects that will also return the other fields in Signup and Attendee? I need all data for a report. I'd prefer not to change my models because this seems like this should be doable the way they are. I'd also like to query Meeting because I have some other stuff in the query that Meeting is needed for (removed for simplicity).
Here's what I currently have:
qs = Meeting.objects.filter(id__in=queryset).prefetch_related('signup').annotate(attendee_signup=F('signup__attendee_signup'))
I keep getting "cannot resolve keyword attendee_signup into field"
I also have tried variations of:
qs = Meeting.objects.filter(id__in=queryset).select_related('signup').filter(attendee__signup=signup_id)
I get "name 'signup_id' is not defined"
I'm brand new to python and Django and I don't know if I need to use F and/or annotate, prefech_related or select_related. I've looked up what each does, but I can't find the right syntax to get this working. I feel like I've tried every combo and I'm just mashing keys at this point. Could someone help please?
If you add related_name='signups' to the Signup.meeting field, then this should work:
meetings = Meeting.objects.filter(id__in=queryset).prefetch_related('signups__attendee')
The above will fetch all meetings, and all signups for those meetings, and all attendees for those signups. From there you can iterate over everything and it will all have already been fetched from the database:
for m in meetings:
for s in m.signups:
for a in s.attendee:
# whatever
(Incidentally, I would recommend changing that related_name from "attendee" to "attendees" as the plural form tends to be more intuitive.)
Hey so I am making a color scheme posting site where people can register and post color schemes they come up with. So far everything is working great, the only thing I have left to do is add a "Like Post" feature. I'm wondering what the best way to implement this would be.
I have two ideas on how this could be done, the first is add an additional field to both the ColorSet (posts) and the User models (for the user model I would set up a new model with a OneToOne relationship to add onto the User model) which would record users that have each single post, and which posts each user has liked to keep track of everything.
So this could look something like this:
from django.db import models
from django.core.urlresolvers import reverse
from django.conf import settings
from django.contrib.auth import get_user_model
User = get_user_model()
# Create your models here.
class ColorSet(models.Model):
user = models.ForeignKey(User,related_name='colorset')
published_date = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=50,blank=False)
color_one = models.CharField(max_length=6,blank=False,default='cccccc')
color_two = models.CharField(max_length=6,blank=False,default='ffffff')
color_three = models.CharField(max_length=6,blank=False,default='e5e5e5')
color_four = models.CharField(max_length=6,blank=False,default='f0f0f0')
color_five = models.CharField(max_length=6,blank=False,default='bababa')
liked_by = models.IntegerField(blank=True)
def publish(self):
self.save()
def get_absolute_url(self):
return reverse('index')
def __str__(self):
return self.name
user model:
from django.db import models
from django.contrib.auth.models import User
# Create your models here.
class UserStats(models.Model):
user = models.OneToOneField(User)
liked_sets = models.IntegerField(blank=True)
def __str__(self):
return self.user.username
In this first option I would have the new model fields (liked_sets and liked_by) be equal to lists containing the ok's of all the Color Sets each user has liked and all the users who have liked each post respectively.
The other way that I'm thinking about would be to just create an entirely new model that tracks the likes for each color set post (not totally sure how this model would look yet exactly).
Aside from which is easier, I wondering which makes more sense from a technical standpoint? Will one of these two options take up more space or create heavier server load?
Thanks for the help.
As far as I understand your problem, it can be broken in two parts.
Maintaining the total number of likes on a model ColorSet.
Keeping the track of all those users who liked a single instance of ColorSet.
Now if I understand your problem correctly(correct me if I'm wrong), when you say:
new model fields (liked_sets and liked_by) be equal to lists containing the ok's of all the Color Sets each user has liked and all the users who have liked each post respectively.
you intend to create a field in your database which would simply store a list of pks of all the people who've liked a ColorSet model instance. Even if you don't intend to do that, still an IntegerField to store such information is(in my humble opinion) somewhat wrong.
Now why you wouldn't want to do that? It's because relational databases are made to recognize the relations between tuples of information and enhance the processing by creating relations. That is why we use relations like OneToOneField and ForeignKey. They make the processing way faster. If we were to simply store the pk values in a Field, further search them in our database to retrieve information, that would be something really slow.
Now I suppose what you are looking for is ManyToManyField.
In your problem, you will simply map the ManyToManyField it to the User model.
It would look something like:
class ColorSet(models.Model):
user = models.ForeignKey(User,related_name='colorset')
published_date = models.DateTimeField(auto_now_add=True)
name = models.CharField(max_length=50,blank=False)
color_one = models.CharField(max_length=6,blank=False,default='cccccc')
color_two = models.CharField(max_length=6,blank=False,default='ffffff')
color_three = models.CharField(max_length=6,blank=False,default='e5e5e5')
color_four = models.CharField(max_length=6,blank=False,default='f0f0f0')
color_five = models.CharField(max_length=6,blank=False,default='bababa')
liked_by = models.IntegerField(blank=True)
#add a simple ManyToManyField which will hold all the users who liked this colorset
likers = models.ManyToManyField(User , related_name = 'liked_colorsets')
def publish(self):
self.save()
def get_absolute_url(self):
return reverse('index')
def __str__(self):
return self.name
and remove your UserStats model to
Now use the following code outline structure to access the information from the database.
1) To get the ColorSets liked by a User:
#obtain any user model object; for example: user_object = User.objects.get(...)
user_object.liked_colorsets.all()
#a queryset with all the liked colorsets is returned.
2) To get the Users who liked a ColorSet:
#obtain any colorset model object; for example: colorset_object = ColorSet.objects.get(...)
colorset_object.likers.all()
#a queryset with all the Users who liked this colorset is returned.
One more thing that I would like to add here. After a User likes a ColorSet, you would obviously want to add this User to the likers field in your ColorSet model(and increment the liked_by field; I assume you'll manage that). To add a User in the likers field:
#obtain any colorset model object; for example: colorset_object = ColorSet.objects.get(...)
#obtain the user model object of the user who liked this colorset in user_object
#and do
colorset_object.likers.add(user_object)
Read more about adding the models in ManyToManyField here in docs.
Hope this helps. Thanks.
I have my user table in django, and to differ all the users I created two tables, (Teacher and Student).
Both tables are getting an fk from user
So, in order to make authorization how do I check if one's user is in a certain table.
I need to check it this way
def test_func(self):
return self.request.user.check..if..it..exists..in..table
My models are like this.
class Teacher(models.Model):
User = models.OneToOneField(settings.AUTH_USER_MODEL)
This depends on how your models are set up.
If your Teacher model looks something like this;
class Teacher(models.Model):
user = models.ForeignKey(User)
Then you should be able to check if the user is a teacher by using the implicit backref;
self.request.user.teacher_set.exists()
As the question has been updated to show that the model is slightly different than I anticipated, here is an update.
class Teacher(models.Model):
user = models.OneToOneField(User)
Which means that the backref will be a little different.
hasattr(self.request.user, "teacher")
As you've mentioned that you are doing this inside a django template, I'm pretty sure that the following will work:
{% if user.teacher %}
Since you haven't posted your models, I am giving you a rough idea how to do it.
in your views.py -
from .models import Teacher,Student
def test_func(request):
user = request.user
if (Teacher.objects.filter(user=user).count() > 0) or (Student.objects.filter(user=user).count > 0):
#do your stuffs here..
One way is to query both tables:
teacher = Teacher.objects.filter(user=self.request.user)
student = Student.objects.filter(user=self.request.user)
if teacher or student:
# do what you want.
If you put in your relation the argument "related_name" you can do it using inverse relationship
class SomeTable(models.Model):
user = models.ForeignKey(
User, #Your user model or Django one
verbose_name = "User",
related_name = "inverse_relation_name"
)
Then you have to call using keyword arguments for the filters:
SomeTable.inverse_relation_name.filter(id=self.request.user.id) #You will get a queryset
Or
SomeTable.inverse_relation_name.get(id=self.request.user.id) # You will get the object or a exception
After looking for a way to check if a model instance can be deleted in django, I found many options, but none was working as expected. Hope this solution can help.
Let start by creating an Abstract model class which can be inherited by other model
class ModelIsDeletable(models.Model):
name = models.CharField(max_length=200, blank=True, null=True, unique=True)
description = models.CharField(max_length=200, blank=True, null=True)
date_modified = models.DateTimeField(auto_now_add=True)
def is_deletable(self):
# get all the related object
for rel in self._meta.get_fields():
try:
# check if there is a relationship with at least one related object
related = rel.related_model.objects.filter(**{rel.field.name: self})
if related.exists():
# if there is return a Tuple of flag = False the related_model object
return False, related
except AttributeError: # an attribute error for field occurs when checking for AutoField
pass # just pass as we dont need to check for AutoField
return True, None
class Meta:
abstract = True
Example
So let say we have three model Organization and Department and StaffType
So many Department can be in an Organization
And an Organization has a particular StaffType
class StaffType(ModelIsDeletable):
pensionable = models.BooleanField(default=False)
class Organization(ModelIsDeletable):
staff_type = models.ForeignKey(to=StaffType)
class Department(ModelIsDeletable):
organization = models.ForeignKey(to=Organization, to_field="id")
so let say after adding some information you want to remove an organization model instance
that is already tied to a Department
For instance we have
Organization Table => (name = Engineering, pk = 1)
Department Table => (name=Developer, organization_fk=1, pk=1)
Now when you try to delete an organization after get it with the pk
a_org = Organization.objects.get(pk=1)
With this at hand you can check if it deletable
deletable, related_obj = a_org.is_deletable()
if not deletable:
# do some stuff with the related_obj list
else:
# call the delete function
a_org.delete()
Your question appears to be "How to detect what related model objects would be deleted if I delete this model object?" or "How to detect what related rows would be deleted if I delete this row?"
Another option is to use a transaction, do the delete, save the information provided by django, but rollback before committing the change. This works in databases like Postgres and MySQL, but I don't know about others.
In this example I want to know what will be deleted if I delete my organization named 'pdemo', and I see that it has 408 related Property objects.
https://gist.github.com/cgthayer/25aa97bb4b74efb75e3467fb7bbdaacb
>>> from django.db import transaction
>>> transaction.set_autocommit(autocommit=False)
>>> o = Organization_v2.objects.get(name='pdemo')
>>> del_info = o.delete()
>>> del_info
(1404, {'data.Property': 408, [..more stuff..], 'data.Organization_v2': 1})
>>> Property.objects.filter(scope__organization_id=o).count()
0
>>> transaction.rollback()
>>> o = Organization_v2.objects.get(name='pdemo')
>>> Property.objects.filter(scope__organization_id=o).count()
408
This could be translated into a generic function.
When looking into this, I found many old solutions that use the functions in the django.contrib.admin to determine this, but that's an undocumented api that seems to change from time to time, so using transactions seems to be easier iff your database supports it.
I want the ability to let users indicate what countries they have visited.. my models.py looks something like this:
class User(models.Model):
name = models.CharField(max_length=50)
countries = models.ManyToManyField(Countries)
class Countries(models.Model):
#This is where I don't know what to do.
#The end goal is for the user to be able to check off what countries he/she has visited
You would create the relationship the other way around
class User(models.Model):
name = models.CharField(max_length=50)
class Countries(models.Model):
user = models.ForeignKey(User)
If you are using django's built in User stuff then you only need this.
from django.contrib.auth.models import User
class Countries(models.Model):
user = models.ForeignKey(User)
Relation fields already generate an attribute on the other model for the reverse relation unless explicitly disabled.
You're fine as is w/r/t the ManyToManyField.
Now you'll want to create a form for this model to allow the checking-off to be done by your users.