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)
Related
My models are using GenericForeignKey and GenericRelations. For simplification my models structure is following:
class BaseModel(models.Model):
name = models.CharField(max_length=100)
model_labels = models.GenericRelation('ModelLabel', related_query_name='%(class)s')
model_types = models.GenericRelation('ModelType'), related_query_name='%(class)s')
class Meta:
abstract = True
class ModelA(BaseModel):
fieldA = models.CharField(max_length = 50)
class ModelB(BaseModel):
fieldB = models.CharField(max_length = 50)
class ModelLabel(models.Model);
label = models.CharField(max_length = 50)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
model_object = GenericForeignKey('content_type', 'object_id')
class ModelType(models.Model):
type = models.CharField(max_length = 50)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
model_object = GenericForeignKey('content_type', 'object_id')
class Unrelated(models.Model):
name = models.CharField(max_lenght = 50)
The problem I am facing is in admin view for ModelLabel and ModelType. Right now, out-of-the-box I am getting 3 fields: Label or Type, Content type and Object id. Yes, this is correct according to the model.
But in Content type I am getting not only ModelA and ModelB as an options. I'm also getting Unrelated and most of the individual fields of different models. According to documentation:
Instances of ContentType represent and store information about the
models installed in your project, and new instances of ContentType are
automatically created whenever new models are installed.
According to this Unrelated can appear in the values, but models' fields shouldn't. So why they appear?
However, this is not the main problem I am struggling with. I'd like to narrow the list of options to choose from in Content type just to ModelA and ModelB. From what I gathered since this is ForeignKey under the hood I should be able to go with ModelChoiceField, but I would need to make it use some hardcoded QuerySet but I cannot find how to make it e.g. from dictionary. Similar goes with Object id, where I'd like to be able to select what value I want to use based on available numbers.
So far I realized that most likely I need to extend admin.ModelAdmin view and most likely also create custom form (extend forms.ModelForm). This is also theory because so far I couldn't figure a way to get mentioned custom QuerySet or Object id. Also there is some drawback of this approach, because if I'll choose to go with this, I'll need to have custom form for every model (ModelLabel, ModelType) - is this really necessary?
I know of a way to add GenericInlineModelAdmin class, but this view allows to add object of type ModelLabel together with ModelB. This doesn't cover case where object of type ModelB already exists.
To summarize what are main goals I want to achieve:
Fix Admin view to create ModelLabel and ModelType
Narrow list of options for Content type field to ModelA and ModelB
Allow user to choose only existing IDs for Object id field
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
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 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 those two models in my Django app:
class ItemOrigin(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey()
[...]
class Place(models.Model):
name = models.CharField(max_length=256)
origins = generic.GenericRelation(ItemOrigin)
[...]
In Django 1.4, the following query used to work:
ItemOrigin.objects.values_list('id', 'place')
But since upgrading to Django 1.6, that query generates the following error message:
FieldError: Cannot resolve keyword 'place' into field. Choices are: content_type, created, dubitable, evidence, historical_item, id, legacy_id, modified, object_id
I may have missed something in the upgrade notes but I cannot see any mention of a compatibility break related to this kind of queries.
So my questions are: why isn't it working any more and, more importantly, how can I fix it so the same query will work. I'd prefer a solution where the query code is not modified (much) as it's much harder to change than the model definitions in my app. This is because my querying system in dynamic and the list of fields passed in values_list is derived from a config file.
That worked in Django < 1.6 but was untested and undocumented. There is a open ticket here.
For a workaround, you can do this:
ctype = ContentType.objects.get_for_model(Place)
pk_list = ItemOrigin.objects.filter(
content_type=ctype
).values_list('object_id', flat=True)
places = Place.objects.filter(pk__in=pk_list)