Django filter on generic relationship - django

I have a model below which points to a generic relationship. This can either be a contact object or a customer object.
class Unsubscribe(models.Model):
"""
Notes:
See: http://www.screamingatmyscreen.com/2012/6/django-and-generic-relations/
"""
content_type = models.ForeignKey(ContentType, help_text="Represents the name of the model")
object_id = models.PositiveIntegerField(help_text="stores the object id")
content_object = generic.GenericForeignKey('content_type', 'object_id')
reason = models.CharField(max_length=60)
request_made = models.DateTimeField(auto_now_add=True,
help_text="Shows when object was created.")
class Meta:
ordering = ['-request_made']
I would like to list out all unsubscribed both unsubscribe customers and contacts only for the user.
queryset = Unsubscribe.objects.filter()
Above gives me all unsubscribe customers and contacts for any users normally I would solve this by doing....
queryset = Unsubscribe.objects.filter(user=request.user)
However, Unsubscribe object does not have a user, but both customers and contacts do.
So how can I filter on the generic relationship?

You could try this
Unsubscribe.objects.filter(content_type__name='user', user=request.user)
For content_type__name='user' specify name of your model class for user or whatever you have associated it with.
Or this also
Unsubscribe.objects.filter(content_type__name='user', object_id=request.user.id)

I assume your model is like:
class Contact(models.Model):
...
user = models.ForeignKey(User)
You can get all the contacts related to the current user using:
contact_ids = [each.id for each in request.user.contact_set.all()]
You can get all the unsubscribed contacts for that user:
unsubscribed_contacts = Unsubscribe.objects.filter(content_type__name='contact', object_id__in=contact_ids)

Remember that a generic foreign key is just two fields, one that is a ForeignKey to a ContentType model, and the other which is the primary key of whichever model you're pointing to. So, to be more generic, you could do something like this:
content_type = ContentType.objects.get_for_model(User)
Unsubscribe.objects.filter(content_type=content_type, object_id=user.id)

for newcommers to this question that have django newer versions , content_type__name is just a property and you can't query with that. instead use content_type__model in your filter methods just like that:
Unsubscribe.objects.filter(content_type__model='user', user=request.user)

Related

How to show a related_name field from Django models connected through a ManyToMany relationship?

New to Django and relational DBs. I'm building the classic Doctor appointment booking app and have come to a point where I don't know what to do. I've created the Doctor model pointing to a Clinic, but in my API the Clinic model won't show a list of all Doctors. How could I achieve this?
class Clinic(models.Model):
name = models.CharField(max_length=200)
class Doctor(models.Model):
clinic = models.ManyToManyField(Clinic, related_name="doctors")
class ClinicSerializer(serializers.ModelSerializer):
class Meta:
model = Clinic
fields = '__all__'
class DoctorSerializer(serializers.ModelSerializer):
class Meta:
model = Doctor
fields = '__all__'
class ClinicViewset(viewsets.ModelViewSet):
queryset = Clinic.objects.all()
serializer_class = ClinicSerializer
class DoctorViewset(viewsets.ModelViewSet):
queryset = Doctor.objects.all()
serializer_class = DoctorSerializer
Django will automatically create the link from Clinic to Doctor. You don't need to (shouldn't) define it. From the docs:
Django also creates API accessors for the “other” side of the relationship – the link from the related model to the model that defines the relationship. For example, a Blog object b has access to a list of all related Entry objects via the entry_set attribute: b.entry_set.all().
The related_name argument that you passed to ManyToManyField when you created the clinic field is the name of the relation from Clinic to Doctor. It is optional and if you didn't pass it, it would be the lower-cased named of the model + _set - doctor_set in your case.
Usually you would set it to a plural in the case of ManyToManyField. In your case: doctors.
Because you have related_name="doctor" currently, you can retrieve a clinic's doctors with: clinic.doctor.all().

How can I create model instance via Serializer without creating models from nested serialziers?

I have model with many links into it:
class Travel(BaseAbstractModel):
tags = models.ManyToManyField(
Tag,
related_name='travels',
)
owner = models.ForeignKey(
'users.TravelUser',
related_name='travel_owner'
)
payment = models.ForeignKey(
Payment,
related_name='travels',
)
country = models.ForeignKey(
Country,
related_name='travels,
)
........
Many of these models have only two fields with unique name and image.
I create serializer for each of these models and put them in TravelSerializer
class TravelBaseSerializer(DynamicFieldsModelSerializer):
owner = UserSerializer(required=False)
tags = TagSerializer(many=True)
payment = PaymentSerializer()
country = CountrySerializer()
Based on docs I override create() and update.
The problem is, when I sent JSON data, Django create each model from nested serializers. But I want to create only Travel instance. Also I want receive and respond serialized object not only pk field.
UPDATE
I solved this problem, put code in the answer. Now I can receive and respond with Serializer data without creating object.
But I think the DRF provides more elegant approach then I do. It is my first project with DRF, maybe I miss something and there's an easier solution.
I decide override to_internal_value() put it in custom serailizer and inherit all nested serializers from it:
class NestedRelatedSerializer(serializers.ModelSerializer):
def to_internal_value(self, data):
try:
pk = data['pk']
except (TypeError, KeyError):
# parse pk from request JSON
raise serializers.ValidationError({'_error': 'object must provide pk!'})
return pk
Get all pk from it and save in create and updated methods:
def update(self, instance, validated_data):
# If don't get instance from db, m2m field won't update immediately
# I don't understand why
instance = Travel.objects.get(pk=instance.pk)
instance.payment_id = validated_data.get('payment', instance.payment_id)
instance.country_id = validated_data.get('country', instance.country_id)
# update m2m links
instance.tags.clear()
instance.tags.add(*validated_data.get('tags'))
instance.save()
return instance
I'm not exactly sure I understand what you want to do, but could setting read_only_fields is the Meta class be what you need ?
class TravelBaseSerializer(DynamicFieldsModelSerializer):
owner = UserSerializer(required=False)
tags = TagSerializer(many=True)
payment = PaymentSerializer()
country = CountrySerializer()
class Meta:
read_only_fields = ('tags',)
See this section in the docs.

Get attribute of a generic foreign key

I'm still trying to wrap my head around generic foreign keys in Django, so what I've come up with is quite basic so far. I'm trying to create a NotificationRecipient, which has a generic foreign key of 2 different models I've already created. These notification recipients can either be a Client or an Account. I may decide to add more recipients of a new model.
I want to create a get_email method in NotificationRecipient, where it checks if the recipient is a contact, client, or account. Then depending on which model it is, it pulls a different attribute.
My existing models look a little like this:
class Client(models.Model):
primary_email = models.EmailField(blank=True)
...
class Account(AbstractNamedUser):
email = models.EmailField(blank=True)
...
Try to get the email depending on the model:
class NotificationRecipient(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
def get_email_addr(self):
''' Gets the model, then pulls the email address. '''
# Get the model
# if client:
# return primary_email
# elif account:
# return email
How would I go about doing it?
It is best to define a method on your targeted models rather than building all the business logic in your NotificationRecipient model.
The logic is that the NotificationRecipient model only needs to know that it requires an email address.
class Client(...):
def get_email_addr(self):
return primary_email
class Account(...):
def get_email_addr(self):
return email
class NotificationRecipient(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
def get_email_addr(self):
try:
email = self.content_object.get_email_addr()
# if you want to enforce the attribute
except AttributeError:
raise ImproperlyConfigured('Model requires an email address')
# if you need a default
if not email:
return 'default#email.com'
return email
You can check the content_type field to determine which kind the object is.
But rather than checking the type, you might consider defining an attribute on all the target models which returns the relevant attribute.

Django ManyToManyField reverse Relationship

I would like to do a reverse relationship on my table Tickets.
Here is my model :
class Tickets(models.Model):
ticket_title = models.CharField(max_length=100)
ticket_content = models.TextField()
class User_Detail(models.Model):
user = models.OneToOneField(User)
tickets = models.ManyToManyField(Tickets, blank=True, null=True)
I create my ticket like that :
ticket = Tickets.objects.create(ticket_title="test", ticket_content="test content")
request.user.user_detail.tickets.add(ticket)
and the thing I'm having an issue to do is to get the username of the guy who post the ticket, (without request.user)
so I tried like that :
ticket = Tickets.objects.get(pk=1)
ticket.user_detail_set.user.username
but I get
AttributeError: 'ManyRelatedManager' object has no attribute 'user'
Thanks you for watching, I hope you'll understand.
Since you set up a many-to-many relationship, a Ticket may have many User_Detail objects. Therefore, Ticket.user_detail_set is a manager, not a single object. You could get the first user associated with a Ticket like this:
ticket.user_detail_set.first().user.username
But it sounds like you actually want a one-to-many relationship between Ticket and User_Detail, meaning you actually want Ticket to have a foreign key relationship. Your models should probably look like this:
class User_Detail(models.Model):
user = models.OneToOneField(User)
class Ticket(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=100)
contents = models.TextField()
Then you can do:
ticket = Ticket.objects.get(pk=1)
user = ticket.user
You might even be able to drop the User_Detail model entirely, unless you use it elsewhere in your application and/or it has more fields than what is shown here.

django: how do I query based on GenericForeignKey's fields?

I'm new in using GenericForeignKey, and I couldn't make it to work in a query statement. The tables are roughly like the following:
class Ticket(models.Model):
issue_ct = models.ForeignKey(ContentType, related_name='issue_content_type')
issue_id = models.PositiveIntegerField(null=True, blank=True)
issue = generic.GenericForeignKey('issue_ct', 'issue_id')
class Issue(models.Model):
scan = models.ForeignKey(Scan)
A scan creates one issue, an issue generates some tickets, and I made Issue as a foreign key to Ticket table. Now I have a Scan object, and I want to query for all the tickets that related to this scan. I tried this first:
tickets = Tickets.objects.filter(issue__scan=scan_obj)
which doesn't work. Then I tried this:
issue = Issue.objects.get(scan=scan_obj)
content_type = ContentType.objects.get_for_model(Issue)
tickets = Tickets.objects.filter(content_type=content_type, issue=issue)
Still doesn't work. I need to know how to do these kind of queries in django? Thanks.
The Ticket.issue field you've defined will help you go from a Ticket instance to the Issue it's attached to, but it won't let you go backwards. You're close with your second example, but you need to use the issue_id field - you can't query on the GenericForeignKey (it just helps you retrieve the object when you have a Ticket instance). Try this:
from django.contrib.contenttypes.models import ContentType
issue = Issue.objects.get(scan=scan_obj)
tickets = Ticket.objects.filter(
issue_id=issue.id,
issue_ct=ContentType.objects.get_for_model(issue).id
)
Filtering across a GenericForeignKey can by creating a second model that shares the db_table with Ticket. First split up Ticket into an abstract model and concrete model.
class TicketBase(models.Model):
issue_ct = models.ForeignKey(ContentType, related_name='issue_content_type')
issue_id = models.PositiveIntegerField(null=True, blank=True)
class Meta:
abstract = True
class Ticket(TicketBase):
issue = generic.GenericForeignKey('issue_ct', 'issue_id')
Then create a model that also subclasses TicketBase. This subclass will have all the same fields except issue which is instead defined as a ForeignKey. Adding a custom Manager allows it to be filtered to just a single ContentType.
Since this subclass does not need to be synced or migrated it can be created dynamically using type().
def subclass_for_content_type(content_type):
class Meta:
db_table = Ticket._meta.db_table
class Manager(models.Manager):
""" constrain queries to a single content type """
def get_query_set(self):
return super(Manager, self).get_query_set().filter(issue_ct=content_type)
attrs = {
'related_to': models.ForeignKey(content_type.model_class()),
'__module__': 'myapp.models',
'Meta': Meta,
'objects': Manager()
}
return type("Ticket_%s" % content_type.name, (TicketBase,), attrs)