I'm trying to create a followers system in Django.
follows = models.ManyToManyField('self', related_name='follower', symmetrical=False, null=True, blank=True)
Since the system should not be symmetrical, how can I check that two users follow each other?
I want to have a function within the model, if possible. I might use this function within limit_choices_to of another field and some other places.
My initial thought was to add a "friends" field and make it symmetrical, but this can bring other issues later.
I didn't find an easy solution, that won't require additional models.
Here what I've done:
Creating Person(models.Model) is not required, if you have something like CustomUser(AbstractUser) implemented. Otherwise Person class can be useful in many situations.
count_ functions are pretty useful, but not required for most of the use cases.
Inside accounts/models.py I added:
RELATIONSHIP_FOLLOWING = 1
RELATIONSHIP_BLOCKED = 2
RELATIONSHIP_STATUSES = (
(RELATIONSHIP_FOLLOWING, 'Following'),
(RELATIONSHIP_BLOCKED, 'Blocked'),
)
class Person(models.Model):
relationships = models.ManyToManyField('self', through='Relationship',
symmetrical=False,
related_name='related_to')
def get_relationships(self, status):
return self.relationships.filter(
to_people__status=status,
to_people__from_person=self)
def get_related_to(self, status):
return self.related_to.filter(
from_people__status=status,
from_people__to_person=self)
def get_following(self):
return self.get_relationships(RELATIONSHIP_FOLLOWING)
def get_followers(self):
return self.get_related_to(RELATIONSHIP_FOLLOWING)
def count_following(self):
return len(self.get_relationships(RELATIONSHIP_FOLLOWING))
def count_followers(self):
return len(self.get_related_to(RELATIONSHIP_FOLLOWING))
def get_friends(self):
return self.relationships.filter(
to_people__status=RELATIONSHIP_FOLLOWING,
to_people__from_person=self,
from_people__status=RELATIONSHIP_FOLLOWING,
from_people__to_person=self)
def count_friends(self):
return len(self.get_friends())
class Relationship(models.Model):
from_person = models.ForeignKey(Person, related_name='from_people', on_delete=models.CASCADE)
to_person = models.ForeignKey(Person, related_name='to_people', on_delete=models.CASCADE)
status = models.IntegerField(choices=RELATIONSHIP_STATUSES)
def add_relationship(self, person, status):
relationship, created = Relationship.objects.get_or_create(
from_person=self,
to_person=person,
status=status)
return relationship
def remove_relationship(self, person, status):
Relationship.objects.filter(
from_person=self,
to_person=person,
status=status).delete()
return
Related
I am trying to learn Self-referencing many-to-many and stuck on code segment
Let assume two models,Person and relation ship,as below:-
class Person(models.Model):
name = models.CharField(max_length=100)
relationships = models.ManyToManyField('self', through='Relationship',
symmetrical=False,
related_name='related_to')
def __unicode__(self):
return self.name
RELATIONSHIP_FOLLOWING = 1
RELATIONSHIP_BLOCKED = 2
RELATIONSHIP_STATUSES = (
(RELATIONSHIP_FOLLOWING, 'Following'),
(RELATIONSHIP_BLOCKED, 'Blocked'),
)
class Relationship(models.Model):
from_person = models.ForeignKey(Person, related_name='from_people')
to_person = models.ForeignKey(Person, related_name='to_people')
status = models.IntegerField(choices=RELATIONSHIP_STATUSES)
and person model contains following methods:-
def get_relationships(self, status):
return self.relationships.filter(
to_people__status=status,
to_people__from_person=self)
def get_related_to(self, status):
return self.related_to.filter(
from_people__status=status,
from_people__to_person=self)
I am unable to understand how these two methods are working and what they are returning.
I am unable to understand how filter and reverse-lookup are working together.
Please anyone help me to understand real intuition behind this.
Thanks in advance.
Hope to here from you soon.
I am trying to implement the behaviour for my models so that when you are deleting the object(s) it is not deleting physically, but just add some attribute to state that it was deleted.
So I created custom queryset, manager and mixin to apply for each created model:
class StateQuerySet(models.query.QuerySet):
def delete(self):
self.update(active=False)
class StateManager(models.Manager):
def get_queryset(self):
return StateQuerySet(self.model, using=self._db).filter(active=True)
class ModelMixin(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
active = models.BooleanField(default=True)
objects = StateManager()
def delete(self, *args, **kwargs):
self.active = False
self.save()
class Meta:
abstract = True
And the models:
class Organizer(ModelMixin, models.Model):
name = models.CharField(max_length=256)
class EventData(ModelMixin, models.Model):
caption = models.CharField(max_length=512)
description = models.TextField(blank=True, default='')
organizer = models.ForeignKey(Organizer, on_delete=models.CASCADE)
So, the idea, is when I do any:
Organizer.objects.all() / Organizer.objects.filter(name__startswith='<some_start_prefix>')
I will recieve Organizer objects only which are active=True (i.e. "not deleted").
All looks good, but when I have related objects, there is an issue.
If, for example, I do:
EventData.objects.filter(organizer__name__startswith='<some_start_prefix>')
It will return all EventData objects even the 'deleted'.
But if I do:
EventData.objects.filter(organizer__name__startswith='<some_start_prefix>', organizer__active=True)
All works as expected and only 'active' records returned. So I don't want to use:
organizer__active=True
for each query in the views.
I've read the docs but still don't understand how to create this with custom Manager and QuerySet. Could you please help, or guide how to do this? What I am missing?
So to summarize: when I do:
EventData.objects.filter(organizer__name__startswith='<some_start_prefix>')
I want to recieve all EventData objects where Organizer.active set to True, but without writing additional organizer__active=True each time in the client code. Is it possible to do it in the Manager or QuerySet?
Ok, so you can indeed add some custom queries to your model's queryset.
You might do something like;
def filter_active_organizer(self, prefix):
return self.filter(organizer__name__startswith=prefix, organizer__active=True)
Let me illustrate with something from a project I've got;
from django.db.models import Q, QuerySet
from django.utils import timezone
class EventQuerySet(QuerySet):
"""
Custom queryset/manager for the Event model...adds common filtering
operations
"""
def filter_open(self):
"""
Filter events in this queryset to only include those whose entry
process is currently open.
"""
now = timezone.now()
return self.filter(
Q(entry_open__isnull=True) | Q(entry_open__lte=now),
Q(entry_close__isnull=True) | Q(entry_close__gt=now)
)
def filter_closed(self):
"""
Filter events in this queryset to only include those whose entry
process is currently closed.
"""
now = timezone.now()
return self.filter(
(Q(entry_open__isnull=False) & Q(entry_open__gt=now)) |
(Q(entry_close__isnull=False) & Q(entry_close__lte=now))
)
EventManager = EventQuerySet.as_manager()
class Event(models.Model):
class Meta:
app_label = 'entry'
verbose_name = _('Event')
verbose_name_plural = _('Events')
objects = EventManager
These queries can then be accessed using Event.objects.filter_open() or added to admin with a filter class;
class FilterStatus(admin.SimpleListFilter):
"""
List filter to filter events by whether they are open or closed
(this is a computed value)
"""
title = 'entry status'
parameter_name = 'entry_status'
def lookups(self, request, model_admin):
"""
Return the lookup choices for this filter
"""
return [
('open', _('Open')),
('closed', _('Closed')),
]
def queryset(self, request, queryset):
"""
Perform the filtering (if required)
"""
if self.value() == 'open':
return queryset.filter_open()
if self.value() == 'closed':
return queryset.filter_closed()
return queryset
I have these models:
Organisation
Student
Course
Enrollment
A Student belongs to an Organisation
A Student can enrol on 1 or more courses
So an Enrollment record basically consists of a given Course and a given Student
from django.db import models
from model_utils.models import TimeStampedModel
class Organisation(TimeStampedModel):
objects = models.Manager()
name = models.CharField(max_length=50)
def __str__(self):
return self.name
class Student(TimeStampedModel):
objects = models.Manager()
first_name = models.CharField(max_length=50)
last_name = models.CharField(max_length=50)
email = models.EmailField(unique=True)
organisation = models.ForeignKey(to=Organisation, on_delete=models.SET_NULL, default=None, null=True)
def __str__(self):
return self.email
class Course(TimeStampedModel):
objects = models.Manager()
language = models.CharField(max_length=30)
level = models.CharField(max_length=2)
def __str__(self):
return self.language + ' ' + self.level
class Meta:
unique_together = ("language", "level")
class EnrollmentManager(models.Manager):
def org_students_enrolled(self, organisation):
return self.filter(student__organisation__name=organisation).all()
class Enrollment(TimeStampedModel):
objects = EnrollmentManager()
course = models.ForeignKey(to=Course, on_delete=models.CASCADE, default=None, null=False, related_name='enrollments')
student = models.ForeignKey(to=Student, on_delete=models.CASCADE, default=None, null=False, related_name='enrollments')
enrolled = models.DateTimeField()
last_booking = models.DateTimeField()
credits_total = models.SmallIntegerField(default=10)
credits_balance = models.DecimalField(max_digits=5, decimal_places=2)
Notice the custom EnrollmentManager that allows me to find all students who are enrolled from a given organisation.
How can I add a custom Manager to retrieve all the courses from a given organisation whose students are enrolled?
What I have tried
I thought to create a CourseManager and somehow query/filter from that side of the relationship:
class CourseManager(models.Manager):
def org_courses_enrolled(self, organisation):
return self.filter(enrollment__student__organisation__name=organisation).all()
This works, but it gives me the same 100 enrollment records :(
What I am trying to get is:
based on a given organisation
find all students who are enrolled
and then (DISTINCT?) to get the list of enrolled courses for that org
This is the view:
class OrganisationCoursesView(mixins.ListModelMixin, mixins.RetrieveModelMixin, viewsets.GenericViewSet):
serializer_class = CourseSerializer
queryset = Course.objects.get_courses(1)
and the url:
# The below should allow: /api/v1/organisations/1/courses/
router.register('api/v1/organisations/(?P<organisation_pk>\d+)/courses', OrganisationCoursesView, 'organisation courses')
UPDATE 1
Based on the answer from h1dd3n I tried this next:
class CourseManager(models.Manager):
def get_queryset(self):
return super(CourseManager, self).get_queryset()
def get_courses(self, organisation):
return self.get_queryset().filter(student__organisation_id=organisation)
but that throws an error (as I expected it would):
FieldError: Cannot resolve keyword 'student' into field. Choices are:
courses, created, id, language, level, modified, progress
UPDATE 2 - getting closer!
Ok with help from #AKX's comments:
class CourseManager(models.Manager):
def get_queryset(self):
return super(CourseManager, self).get_queryset()
def get_courses(self, organisation):
return self.get_queryset().filter(courses__student__organisation_id=organisation)
now DOES return courses, but it returns a copy for each enrolled student. So now I need to group them so each record only appears one time...
First you need to change self.filter to self.get_queryset().filter() or make a seperate method in the manager.
def get_queryset(self):
return super(CourseManager, self).get_queryset()
In manager create a function
def get_courses(self,organisation):
return self.get_queryset.filter(student__oraganisation=organisation)
This should return the students and you don't need to call .all() - the filtered qs either way returns you all the objects that it finds.
EDIT
Try this:
class CourseManager(models.Manager):
def get_queryset(self):
return super(CourseManager, self).get_queryset()
def get_courses(self, organisation):
return self.get_queryset().filter( \
enrollments__student__organisation_id=organisation).distinct()
UPDATE 2
You can try and play around with from django.db.models import Q https://docs.djangoproject.com/en/3.0/topics/db/queries/
or with annotate based on this answer Get distinct values of Queryset by field where you filter out each student so it would appear once.
How can I can use managers to query my foreign key and then retrieve objects that are connected to foreign key?
Here is my models.py:
from django.db import models
# Create your models here.
class BookManager(models.Manager):
def title_count(self,keyword):
return self.filter(title__icontains=keyword).count()
class CategoryManager(models.Manager):
def category_count(self):
return self.filter(category__icontains=python).count()
class Category(models.Model):
title=models.CharField(max_length=20)
def __str__(self):
return self.title
class Enquiry(models.Model):
title=models.CharField(max_length=200)
category=models.ForeignKey(Category ,default=False,blank=False)
detail=models.TextField()
objects = BookManager()
objects=CategoryManager()
# tags=models.ChoiceField()
def __str__(self):
return self.title
I tried to use category manager but it gave me a strange error.
I just want to know how exactly we can get the objects that are connected with category foriegn-key and show them as list to the users.
You can combine both the title_count and category_count methods under one Manager. When filtering through a ForeignKey, your field lookup needs to specify the name of the field you're trying to filter on, else it will assume you're querying the ID field. So instead of category__icontains, you would do category__title__icontains.
Another note, in newer versions of Django, you are required to specify the on_delete parameter when defining a ForeignKey.
This is a working example of what I think you're trying to accomplish.
class BookManager(models.Manager):
def title_count(self, keyword):
return self.filter(title__icontains=keyword).count()
def category_count(self, keyword):
return self.filter(category__title__icontains=keyword).count()
class Category(models.Model):
title = models.CharField(max_length=20)
def __str__(self):
return self.title
class Enquiry(models.Model):
title = models.CharField(max_length=200)
category = models.ForeignKey(Category,
default=False,
blank=False,
on_delete=models.CASCADE)
detail = models.TextField()
books = BookManager()
# tags=models.ChoiceField()
def __str__(self):
return self.title
Here's how you would use it:
Enquiry.books.category_count('python')
Enquiry.books.title_count('test')
edit: I completely rewrote the question as the original one didn't clearly explain my question
I want to run a function which is specific to each particular model instance.
Ideally I want something like this:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = models.FunctionField() #stores a function specific to this instance
x = MyModel(data='originalx', perform_unique_action=func_for_x)
x.perform_unique_action() #will do whatever is specified for instance x
y = MyModel(data='originaly', perform_unique_action=func_for_y)
y.perform_unique_action() #will do whatever is specified for instance y
However there is no datatype FunctionField. Normally this would be solvable with inheritance, and creating subclasses of MyModel, maybe like this:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = default_function
class MyModelX(MyModel):
perform_unique_action = function_X
class MyModelY(MyModel):
perform_unique_action = function_Y
x = MyModelX(data='originalx')
x.perform_unique_action() #will do whatever is specified for instance x
y = MyModelY(data='originaly')
y.perform_unique_action() #will do whatever is specified for instance y
Unfortunately, I don't think I can use inheritance because I am trying to access the function this way:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = default_function
class SecondModel(models.Model):
other_data = models.IntegerField()
mymodel = models.ForeignKey(MyModel)
secondmodel = SecondModel.objects.get(other_data=3)
secondmodel.mymodel.perform_unique_action()
The problem seems to be that I don't know what type the foreign key is going to be in SecondModel if I override the perform_unique_action in subclasses.
Can I access MyModel from SecondModel as a foreign key and still have a unique function for each instance of MyModel?
This works for me. I haven't tested it, but you should be able to create another class and override their methods and it'll work. Check the class Meta line, it'll treat it as an abstract class. Here's an example of my actual classes that I'm working on right now.
EDIT: Added VoteComment class and tested it. It works as expected!
class Vote(models.Model):
VOTE_ENUM = (
(VoteEnum.DOWN_VOTE, VoteEnum.toString(VoteEnum.DOWN_VOTE)),
(VoteEnum.NONE, VoteEnum.toString(VoteEnum.NONE)),
(VoteEnum.UP_VOTE, VoteEnum.toString(VoteEnum.UP_VOTE)),
)
question = models.ForeignKey(Question, null=False, editable=False, blank=False)
voter = models.ForeignKey(User, blank=False, null=False, editable=False)
vote_type = models.SmallIntegerField(default=0, null=False, blank=False, choices=VOTE_ENUM)
class Meta:
abstract = True
def is_upvote(self):
return self.vote_type > 0
def is_downvote(self):
return self.vote_type < 0
class VoteAnswer(Vote):
answer = models.ForeignKey(Answer, null=False, editable=False, blank=False)
class Meta:
unique_together = (("voter", "answer"),) # to prevent user from voting on the same question/answer/comment again
def __unicode__(self):
vote_type = "UP" if vote_type > 0 else ("DOWN" if vote_type < 0 else "NONE")
return u"{0}: [{1}] {2}".format(user.username, vote_type, answer.text[:32])
def is_upvote(self):
return "FOO! "+str(super(VoteAnswer, self).is_upvote())
class VoteComment(Vote):
comment = models.ForeignKey(Comment, null=False, editable=False, blank=False)
class Meta:
unique_together = (("voter", "comment"),) # to prevent user from voting on the same question/answer/comment again
def __unicode__(self):
vote_type = "UP" if vote_type > 0 else ("DOWN" if vote_type < 0 else "NONE")
return u"{0}: [{1}] {2}".format(user.username, vote_type, comment.text[:32])
def is_upvote(self):
return "BAR!"
I came up with two ways of having a specific function defined for each object. One was using marshal to create bytecode which can be stored in the database (not a good way), and the other was by storing a reference to the function to be run, as suggested by Randall. Here is my solution using a stored reference:
class MyModel(models.Model):
data = models.CharField(max_length=100)
action_module = models.CharField(max_length=100)
action_function = models.CharField(max_length=100)
class SecondModel(models.Model):
other_data = models.IntegerField()
mymodel = models.ForeignKey(MyModel)
secondmodel_obj = SecondModel.objects.get(other_data=3)
#The goal is to run a function specific to the instance
#of MyModel referred to in secondmodel_obj
module_name = secondmodel_obj.mymodel.action_module
func_name = secondmodel_obj.mymodel.action_function
module = __import__(module_name)
func = vars(module)[func_name]
func()
Thanks to everyone who replied, I couldn't have got to this answer if it weren't for your help.
You could achive some similar behavior overriding the save method. And providing special callbacks to your instances.
Something like:
def default_function(instance):
#do something with the model instance
class ParentModel(model.Model):
data = models.CharField()
callback_function = default_function
def save(self, *args, **kwargs):
if hasattr(self, 'callback_function'):
self.callback_function(self)
super(ParentModel, self).save(*args, **kwargs)
class ChildModel():
different_data = models.CharField()
callback_function = other_fun_specific_to_this_model
instance = ChildModel()
#Specific function to this particular instance
instance.callback_function = lambda inst: print inst.different_data
instance.save()
You can write endpoints on your server and limit their access to just your self. Then store in each model instance corresponding url. For example:
views.py
def funx_x(request):
pass
def func_y(request):
pass
models.py:
class MyModel(models.Model):
data = models.CharField(max_length=100)
perform_unique_action = models.URLField()
and then:
x = MyModel(data='originalx', perform_unique_action='http://localhost/funx_x')
requests.post(x.perform_unique_action)
i dont know whether i understand u correct or not. but you can check out this example here.
Example:
A string representing an attribute on the model. This behaves almost the same as the callable, but self in this context is the model instance. Here's a full model example:
class Person(models.Model):
name = models.CharField(max_length=50)
birthday = models.DateField()
def decade_born_in(self):
return self.birthday.strftime('%Y')[:3] + "0's"
decade_born_in.short_description = 'Birth decade'
class PersonAdmin(admin.ModelAdmin):
list_display = ('name', 'decade_born_in')