Django: unable to create Generic Foreign key object - django

I'm using Django 2.x
I have two models
class DynamicUrlObject(models.Model):
content_type = models.ForeignKey(ContentType, null=True, on_delete=models.SET_NULL)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey('content_type', 'object_id')
domain = models.CharField(max_length=255)
and
class MyModel(models.Model):
name = models.Char(max_length=50)
my_obj = fields.GenericRelation(DynamicUrlObject, related_query_name='my_obj')
I have an object of MyModel and want to create a record for DynamicUrlObject and link same to the MyModel model.
I'm doing it something like
dy_obj = DynamicUrlObject.objects.get_or_create(
content_object=my_existing_obj,
domain='http://example.com'
)
my_existing_obj.my_obj = dy_obj
my_existing_obj.save()
But this is not creating a record for DynamicUrlObject and gives an error as
django.core.exceptions.FieldError: Field 'content_object' does not generate an
automatic reverse relation and therefore cannot be used for reverse querying.
If it is a GenericForeignKey, consider adding a GenericRelation.

You cannot filter or get directly on a generic foreign key [1], so get_or_create() won't work. If you know the type of my_existing_obj is MyModel, you can use the GenericRelation you set on MyModel:
try:
dy_obj = DynamicUrlObject.objects.get(my_obj=my_existing_object, domain=...) # use the `related_query_name` here
except DynamicUrlObject.DoesNotExist:
dy_obj = DynamicUrlObject(content_object=my_existing_object, domain=...)
dy_obj.save()
Also once you've created dy_obj, you don't need to assign the reverse relationship to my_existing_object. The GenericRelation isn't a concrete field in the db, it's just a way for django ORM to know how to name the relationships.
[1] https://docs.djangoproject.com/en/2.2/ref/contrib/contenttypes/#django.contrib.contenttypes.fields.GenericForeignKey

Related

Dealing GenericRelation and GenricForeignKey inside migrations

I have models with GenricForeigKey and GenericRelation fields.
class Datasheet(models.Model):
package1 = GenericRelation('PackageInstance')
...
class PackageInstance(models.Model):
content_object = GenericForeignKey()
object_id = models.PositiveIntegerField(null=True)
content_type = models.ForeignKey(ContentType, null=True, on_delete=models.CASCADE)
....
I am migrating from another models, inside my migration I want to create new instance.
for ds in Datasheet.objects.all():
pi = PackageInstance.objects.create(content_object=ds)
However this fails
TypeError: DesignInstance() got an unexpected keyword argument 'content_object'
Additionally, ds.package1.all() will also fail.
AttributeError: 'Datasheet' object has no attribute 'package1'
How do I solve this?
I did some research but did not find a direct answer to my question.
The most important thing to remember is that model methods will not be available in migrations. This includes fields created by the Content Types framework. However, object_id and content_type will be there.
My solution is to simply create things by hand.
ContentType = apps.get_model('contenttypes', 'ContentType')
Datasheet = apps.get_model('api', 'Datasheet')
DatasheetContentType = ContentType.objects.get(model=Datasheet._meta.model_name, app_label=Datasheet._meta.app_label)
for ds in Datasheet.objects.all():
di = DesignInstance.objects.create(object_id=ds.id, content_type=DatasheetContentType)

Django filter on generic relationship (unique constraint exception)

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
)

GenericForeignKeys in Intermediate Models

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.

Django Foreign Key Aliasing

I'm trying to be able to alias a column name from a model's foreign key. I want to be able to change 'owner__username' to just 'username' when passing a JSON response.
query_n = Example.objects.values('owner__username','name')
print(query_n[0])
Which prints
{'name': 'e_adam', 'owner__username': 'adam'}
The only renaming of a column I have seen was via annotate() however, that isn't even truly a column (AVG, SUM, ...)
The Example model has a foreign key owner, which is 'auth.User' model.
Any thoughts?
I'm not too sure about this but if you are using django >= 1.7, sounds like you could use annotate to create alias for named arguments in values(). I found a related django ticket, to quote from the latest response:
Just quickly, with the changes to annotate that have landed, it is now
possible to create aliases yourself, and reference them from the
values call:
from django.db.models import F
Model.objects.annotate(my_alias=F('some__long__name__to__alias')) \
.values('my_alias')
aliasName = models.ForeignKey(modelName, to_field='fieldName', on_delete=models.CASCADE)
This is the format for aliasing in Django Foreign Key
Here is full Models page
from django.db import models
# Create your models here.
class Farm(models.Model):
srFarm = models.AutoField(primary_key=True)
name = models.CharField(max_length = 264, unique =True)
address = models.CharField(max_length = 264)
def __str__(self):
temp = '{0.name},{0.address}'
return temp.format(self)
class Batch(models.Model):
srBatch = models.AutoField(primary_key=True)
farmName = models.ForeignKey(Farm, to_field='name', on_delete=models.CASCADE)
def __str__(self):
temp = '{0.farmName}'
return temp.format(self)

django, manytomany through table with genric foreignkey?

Suppose ModelA and (ModelB1, ModelB2 but they don't have common ancestor) has manytomany relationships.
Now I have a through model ModelAtoB.
class ModelAToB(Model):
model_a = models.ForeignKey(ModelA)
content_type=models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
model_b = generic.GenericForeignKey('content_type', 'object_id')
I see ManyToManyField being used in the django doc, how can I use it in my case as well?
class ModelA(Model):
pass
class ModelB(Model): # hum.. there's no ModelB which is a common ancestor of ModelB1, ModelB2
model_a_list = models.ManyToManyField(ModelA, through='ModelAtoB')
You cannot use the ManyToManyField in your case however your through model (ModelAToB) is enough to store the relation.
If you want to query all related ModelBs models to a given ModelAs you would do it somehow like this:
related_b = ModelAToB.objects.filter(model_a=a).prefetch_related('model_b')
b_instances = [rel.model_b for rel in related_b]
If you want all related ModelAs to a given ModelB this would be done this way:
from django.contrib.contenttypes.models import ContentType
model_b_ct = ContentType.objects.get_for_model(b)
related_a = ModelAToB.objects.filter(object_id=b.id, content_type=model_b_ct)
a_instance = [rel.model_a for rel in related_a]
Making a model method might be a handy tweak.
class ModelB(Model)::
def model_a_list(self):
return ModelA.objects.filter(
modelatob__content_type__app_label=self._meta.app_label,
modelatob__content_type__model=self._meta.module_name,
modelatob__object_id=self.id
)
This returns a queryset so you still can do things like .count(), .filter() on it.