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.
Related
I have a model below which points to a generic relationship. This can either be a Post object or a Reply object.
class ReportedContent(models.Model):
reporter = models.ForeignKey(User, on_delete=models.CASCADE)
# Generic relation for posts and replies
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey()
class Meta:
unique_together = ('reporter', 'object_id', 'content_type')
I would like to check if the content_object is already exists before I get a duplicate key value violates unique constraint exception.
Django documentation mentioned that:
# This will fail
>>> ReportedContent.objects.filter(content_object=content)
# This will also fail
>>> ReportedContent.objects.get(content_object=content)
So how can I filter on generic relation? or how can I deal with this exception specifically?
you can filter by object_id and content_type.
just make sure you do it right,
get content_type this way:
from django.contrib.contenttypes.models import ContentType
# ...
content_type = ContentType.objects.get(app_label='name_of_your_app', model='model_name')
for handling the exception :
if ReportedContent.objects.filter(object_id=content.id,content_type=content_type):
raise Exception('your exception message')
I realize this is an old(ish) question, but I thought I'd offer an alternative method in case others run across this post as I did.
Instead of doing a separate .get() on the ContentType model, I just incorporate the app/model names in my filter, like this:
queryset = ReportedContent.objects.filter(
object_id=parent_object.id,
content_type__app_label=app_label,
content_type__model=model_name
)
I'm attempting to create an intermediate model, Permissions, between 'auth.Group' and any other custom models; this will serve as permissions or a means of what is visible to which groups.
I have been able to create an intermediate model, ExamplePermissions, between 'auth.Group' and one model.
class Example(TimeStampable, Ownable, Model):
groups = models.ManyToManyField('auth.Group', through='ExamplePermissions', related_name='examples')
name = models.CharField(max_length=255)
...
# Used for chaining/mixins
objects = ExampleQuerySet.as_manager()
def __str__(self):
return self.name
class ExamplePermissions(Model):
example = models.ForeignKey(Example, related_name='group_details')
group = models.ForeignKey('auth.Group', related_name='example_details')
write_access = models.BooleanField(default=False)
def __str__(self):
return ("{0}'s Example {1}").format(str(self.group), str(self.example))
However, the issue is that this opposes reusability. To create a model that allows any custom model to be associated with it, I implemented a GenericForeignKey in place of a ForeignKey as follows:
class Dumby(Model):
groups = models.ManyToManyField('auth.Group', through='core.Permissions', related_name='dumbies')
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class Permissions(Model):
# Used to generically relate a model with the group model
content_type = models.ForeignKey(ContentType, related_name='group_details')
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
#
group = models.ForeignKey('auth.Group', related_name='content_details')
write_access = models.BooleanField(default=False)
def __str__(self):
return ("{0}'s Content {1}".format(str(self.group), str(self.content_object)))
Upon attempting to make migrations, it errors with:
core.Permissions: (fields.E336) The model is used as an intermediate model by 'simulations.Dumby.groups', but it does not have a foreign key to 'Dumby' or 'Group'.
At first glance, using a GenericForeignKey in an intermediate table seems like a dead end. If this is the case, is there some generally accepted way of handling such a situation besides the cumbersome and redundant approach of creating a custom intermediate model for each custom model?
Do not use ManyToManyField when using GenericForeignKey in your intermediate model; instead, use GenericRelation, so your groups field would be simply declared as:
groups = generic.GenericRelation(Permissions)
See reverse generic relations for more details.
I'm trying to use generic relations, my model looks like this:
class Post(models.Model):
# Identifiers
user = models.ForeignKey(User, unique=False, related_name = 'posts')
# Resource
resource_type = models.ForeignKey(ContentType)
resource_id = models.PositiveIntegerField()
resource = GenericForeignKey('resource_type', 'resource_id')
# Other
date_created = models.DateTimeField(auto_now=False, auto_now_add=True, blank=True)
class Meta:
unique_together = ('resource_type', 'resource_id',)
However, when on my resource I try to get the Post object, using 'SomeResource.posts' the following exception occurs:
Cannot resolve keyword 'content_type' into field. Choices are:
date_created, id, resource, resource_id, resource_type,
resource_type_id, user, user_id
Why is it looking for content_type when I explicitly named it resource_type on my GenericForeignKey?
I don't see it anywhere in the docs right now, but if you look at the source for GenericRelation there are keywords for content_type_field and object_id_field when you create it. So if you create the relation as GenericRelation(object_id_field='resource_id', content_type_field='resource_type') then it should look for the proper fields.
I found this is particularly necessary if you have multiple GenericForeignKey's in a single model and thus cannot use the default names.
You can see the source for 1.11 here: https://github.com/django/django/blob/stable/1.11.x/django/contrib/contenttypes/fields.py#L291
do you have a specific reason for using 'resource' instead of 'content' / 'object' ?
If not, I would suggest to change all 3 lines related to the generic relations like this (as well as Meta):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content = GenericForeignKey('content_type', 'object_id')
I always stick to these field names for generic relations (based on the documentation), even though the documentation mentions the possibility to rename both 'content_type' and 'object_id' (maybe only 'content' must remain unchanged...). I'm not good enough in Django to explain why the behaviour.
Hope it is feasible and works for your project
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.
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)