Model instances without primary key value are unhashable with Djongo - python

I am getting the error Model instances without primary key value are unhashable when trying to remove a model instance from my admin panel.
models.py
from djongo import models
import uuid
PROPERTY_CLASSES = (
("p1", "Video Property"),
("p2", "Page Property"),
("trait", "Context Trait"),
("custom", "Custom Property")
)
EVENT_TYPES = (
("video", "Video Event"),
("track", "Track Event"),
("page", "Page Event")
)
class Device(models.Model):
_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=255)
class Platform(models.Model):
_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=255)
app_name_possibilities = models.TextField(blank=True)
class EventPlatformDevice(models.Model):
_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = ""
event_field = models.ForeignKey('Event', on_delete=models.CASCADE)
applicable_apps = models.ManyToManyField('Platform', blank=True)
applicable_devices = models.ManyToManyField('Device', blank=True)
property_group = models.ManyToManyField('PropertyGroup', blank=True)
custom_properties = models.ManyToManyField('Property', blank=True)
class Event(models.Model):
_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=255, unique=True)
event_type = models.CharField(max_length=20, choices=EVENT_TYPES)
class PropertyGroup(models.Model):
_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=100)
applicable_properties = models.ManyToManyField('Property')
class Property(models.Model):
_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=255)
property_class = models.CharField(max_length=20, choices=PROPERTY_CLASSES)
format_example = models.TextField(blank=True)
notes = models.TextField(blank=True)
The issue lies with the EventPlatformDevice model I believe. The issue I think is when I create a new entry in the Admin panel and setup the relationships between EventPlatformDevice.applicable_apps it gets saved into the MongoDB using a _id:ObjectId("61c25d36cdca07c8a6101044") since that is what Mongo uses. I changed all of my primary keys to use a UUID since that was the only way I could get it to work. Now I think it is having a issue that some of my items are using ObjectId while some are using the _id:Binary('7w6NaYCQQn6BS7jaOXUNZw==', 3) that I am getting from uuid.
Is it possible to define what type the _id field is for everything? That way I can set it to use UUID.
Attached are images showing the data in Compass.
When I did not specify the _id in all my models, I was getting errors when deleting and accessing. It was like Django did not know how to reference the ObjectId. That is why I went with UUID. But since I cannot control the dd.parsers_eventplatformdevice_applicable_apps (2nd pic) it is having issues deleting it.

Is it possible to define what type the _id field is for everything?
I am afraid not. Each driver implementations may implement UUID serialization and deserialization logic differently, which may not be fully compatible with other drivers. (docs).
First, You can configure a UUID Representation, since PyMongo is using a legacy method to encode and decode UUID (0x03 subtype), you can change to cross-language compatible Binary 0x04 subtype. To do so in Djongo, just provide uuidRepresentation in the database settings as a standard (docs):
DATABASES = {
'default': {
'ENGINE': 'djongo',
'NAME': 'dd',
'ENFORCE_SCHEMA': False,
'CLIENT': {
'host': 'mongodb://localhost:27017',
'uuidRepresentation': 'standard',
'waitQueueTimeoutMS': 30000
},
},
}
Second, I would definitely try to make _id works. So the cration of the _id field will be provided by MongoDB server.
If that does not work for you:
_id = models.ObjectIdField(primary_key=True)
then you could try this one:
_id = models.AutoField(auto_created=True, primary_key=True, unique=True)

Related

Django: How to make Id unique between 2 different model classes

Im working on a problem that requires 2 different models to have unique Ids. So that an instance of ModelA should never have the same id as a ModelB instance.
For example, What would be the Django way of making sure these two model types wont have overlapping Ids?
class Customer(models.Model):
name = models.CharField(max_length=255)
class OnlineCustomer(models.Model):
name = models.CharField(max_length=255)
Edit 1:
Here is an example of something that I've done that works, but does not feel correct. Should I be inheriting from a concrete base class?
class UniqueID(models.Model):
pass
def create_unique_id():
try:
UniqueID = UniqueID.objects.create()
except:
# This try except is here to allow migration to pass since Customers need access to this function during migration
# This should never happen
return 0
return UniqueID.id
class Customer(models.Model):
id = models.IntegerField(primary_key=True, default=create_unique_id)
class OnlineCustomer(models.Model):
id = models.IntegerField(primary_key=True, default=create_unique_id)
As Vishal Singh commented, the UUIDField class from Django's Model can be used to create
A field for storing universally unique identifiers.
As mentioned here.
Usage could be as following:
import uuid
from django.db import models
class Customer(models.Model):
id_ = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=255)
class OnlineCustomer(models.Model):
id_ = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=255)

Django filter relationship in models.py

Assuming the following scenario at two seperated apps where I want to count the number of post elements of a specific category at my template:
Categories/models.py
from Posts.models import Post
...
class Category(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=20, verbose_name="Title")
...
#property
def posts(self):
return Post.objects.filter(category_id=self.id).count()
Posts/models.py
class Post(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(verbose_name="Title")
content = models.TextField(verbose_name="Content")
tag = models.CharField(verbose_name="Meta", max_length=70, blank=True)
category = models.ForeignKey(Category, on_delete=models.CASCADE, null=True)
template.html:
<h5>Number of Post elements: {{ category.posts }}</h5>
sadly this always results in the following error:
ImportError: cannot import name 'Post' from partially initialized
module 'Posts.models' (most likely due to a circular import)
This is likely because you import Posts.models in your Categories.models and vice-versa. You can import this in the property itself:
# no from Posts.models import Post
# …
class Category(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=20, verbose_name="Title")
# …
#property
def posts(self):
from Posts.models import Post
return Post.objects.filter(category_id=self.id).count()
But you do not need to import this model. Django automatically creates a relation in reverse. If you do not specify the related_name, that is modelname_set, so you can acccess this with:
# no from Posts.models import Post
# …
class Category(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=20, verbose_name="Title")
# …
#property
def posts(self):
return self.post_set.count()
Note: Python modules are normally written in snake_case, not PerlCase, so it
should be category, not Category.
You should try using the related name in your property:
class Category(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=20, verbose_name="Title")
...
#property
def posts(self):
return self.post_set.count()
Also, for the record, it seems odd to have Category and Post in separate apps. Likely they should be in a single blog app. Circular imports generally imply you have two apps that are too tightly-coupled. In this case, it should probably be just one app.
No need for that property, you can access the related objects by following the foreign key backwards. See the docs
<h5>Number of Post elements: {{ category.post_set.count }}</h5>
Or you can add a property but directly:
#property
def post_count(self):
return self.post_set.count()
And then:
<h5>Number of Post elements: {{ category.post_count }}</h5>
The hint is in the error as you do indeed have a circular import.
Posts.models.py will have from Categories.models import Category at the top as you are using it as a foreign key and as you are now using Post within the posts method of Category it will have from Posys.models import Post
You need to fix this which you can do in one of three ways depending if you have other models or methods using the Post class from within the Categories model.
If you do then you can use one of these two
Change the foreignKey to pass in a string rather than the Category object so
class Post(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
author = models.ForeignKey(User, on_delete=models.CASCADE)
title = models.CharField(verbose_name="Title")
content = models.TextField(verbose_name="Content")
tag = models.CharField(verbose_name="Meta", max_length=70, blank=True)
category = models.ForeignKey("Category", on_delete=models.CASCADE, null=True)
Move the import from the top of Categories/models.py inside the posts method so that it imports locally
class Category(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=20, verbose_name="Title")
#property
def posts(self):
from Posts.models import Post
return Post.objects.filter(category_id=self.id).count()
If you don't, and we can't tell from what you have posted, then you can just use the ```post_set`` related name within the Category class and avoid the import at all. https://docs.djangoproject.com/en/3.0/topics/db/models/#be-careful-with-related-name-and-related-query-name
#property
def posts(self):
return self.post_set.count()
This will also fix the issue

Django UniqueConstraint

Context
I have the models AppVersion, App & DeployApp. In the AppVersion model users can upload APK files to the filesystem. I am using a pre_save signal to prevent uploading APK files with the same version_code for a specific App like this:
#receiver(pre_save, sender=AppVersion)
def prevent_duplicate_version_code(sender, instance, **kwargs):
qs = AppVersion.objects.filter(app_uuid=instance.app_uuid, version_code=instance.version_code)
if qs.exists():
raise FileExistsError("Version code has to be unique for a specific app")
This signal does what I want, except it also raises the error when I am trying to create an object in the bridge-table DeployApp.
Models
# models.py
class App(models.Model):
app_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_index=True)
app_name = models.CharField(max_length=100)
class AppVersion(models.Model):
app_version_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_index=True)
app_uuid = models.ForeignKey(App, on_delete=models.CASCADE, related_name='app_versions')
app_version_name = models.CharField(max_length=100)
version_code = models.IntegerField(blank=True, null=True, editable=False)
source = models.FileField(upload_to=get_app_path, storage=AppVersionSystemStorage())
class DeployApp(models.Model):
deploy_app_uuid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, db_index=True)
app_version = models.ForeignKey(AppVersion, on_delete=models.CASCADE)
device_group = models.ForeignKey(DeviceGroup, on_delete=models.CASCADE)
release_date = UnixDateTimeField()
My guess is that when creating an object of DeployApp the related AppVersion is also saved and thus the pre_save signal is called and raises the Exception.
I also tried to override the save() method for the AppVersion model but the results are the same.
How do I make sure that the Exception only happens upon creating a new AppVersion instance and does not happen when adding or editing a DeployApp instance?
Solved it thanks to Bear Brown his suggestion. I removed the signal and added UniqueConstraint to the AppVersion model like this:
class Meta:
db_table = 'app_version'
constraints = [
models.UniqueConstraint(fields=['app_uuid', 'version_code'], name='unique appversion')
]

Django one of 2 fields must not be null

I have a model similar to this one:
class Person(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
field1= models.IntegerField(null=True)
field2 = models.IntegerField(null=True)
At least one of the fields field1 or field2 must not be null. How I can I verify it in the model?
Model.clean
One normally writes such tests in Model.clean [Django-doc]:
from django.core.exceptions import ValidationError
class Person(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
field1= models.IntegerField(null=True)
field2 = models.IntegerField(null=True)
def clean(self):
super().clean()
if self.field1 is None and self.field2 is None:
raise ValidationError('Field1 or field2 are both None')
Note that this clean method is not validated by default when you .save() a model. It is typically only called by ModelForms built on top of this model. You can patch the .save() method for example like here to enforce validation when you .save() the model instance, but still there are ways to circumvent this through the ORM.
django-db-constraints (not supported by some databases)
If your database supports it (for example MySQL simply ignores the CHECK constraints), SQL offers a syntax to add extra constraints, and a Django package django-db-constraints [GitHub] provides some tooling to specify such constraints, like:
class Person(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
field1= models.IntegerField(null=True)
field2 = models.IntegerField(null=True)
class Meta:
db_constraints = {
'field_null': 'CHECK (field1 IS NOT NULL OR field2 IS NOT NULL)',
}
Update: Django constraint framework
Since django-2.2, you can make use of the Django constraint framework [Django-doc]. With this framework, you can specify database constraints that are, given the database supports this, validated at database side. You thus can check if at least one of the two fields is not NULL with a CheckConstraint [Django-doc]:
class Person(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
field1= models.IntegerField(null=True)
field2 = models.IntegerField(null=True)
class Meta:
constraints = [
models.CheckConstraint(
check=Q(field1__isnull=False) | Q(field2__isnull=False),
name='not_both_null'
)
]
You can use Model.clean() method:
def clean(self):
if self.field1 is None and self.field2 is None:
raise ValidationError(_('field1 or field2 should not be null'))
See https://docs.djangoproject.com/en/2.1/ref/models/instances/#django.db.models.Model.clean
Since Django 2.2, you can use the built-in constraints feature in Django. No need for 3rd party code.

Django unique_together does not allow ForeignKey field across applications when syncdb is executed

I'm trying to create a unique constraint on my TranslationRequest model listed below. TranslationRequest has a foreign key relationship with a MachineTranslator model, which exists in another Django application. When I try to run syncdb, I get the error: Error: One or more models did not validate:
wt_articles.translationrequest: "unique_together" refers to translator, a field that doesn't exist. Check your syntax.
When I remove translator from the unique_constraint specification, syncdb runs correctly. Note: I'm using Sqlite3 as my back-end database.
Here are the definitions of TranslationRequest and SourceArticle.
from wt_translation.models import MachineTranslator
class TranslationRequest(models.Model):
article = models.ForeignKey(SourceArticle)
target_language = models.ForeignKey(Language, db_index=True)
date = models.DateTimeField(_('Request Date'))
translator = models.ForeignKey(MachineTranslator),
status = models.CharField(_('Request Status'),
max_length=32,
choices=TRANSLATION_STATUSES)
class Meta:
unique_together = ("article", "target_language", "translator")
class SourceArticle(models.Model):
title = models.CharField(_('Title'), max_length=255)
language = models.ForeignKey(Language, db_index=True)
timestamp = models.DateTimeField(_('Import Date'), default=datetime.now())
doc_id = models.CharField(_('Document ID'), max_length=512)
source_text = models.TextField(_('Source Text'))
sentences_processed = models.BooleanField(_('Sentences Processed'))
Here is the definition of MachineTranslator, in a different (but referenced Django application).
class MachineTranslator(models.Model):
shortname = models.CharField(_('Name'), max_length=50)
supported_languages = models.ManyToManyField(LanguagePair)
description = models.TextField(_('Description'))
type = models.CharField(_('Type'), max_length=32, choices=TRANSLATOR_TYPES, default='Serverland'),
timestamp = models.DateTimeField(_('Refresh Date'), default=datetime.now())
is_alive = models.BooleanField()
Not all of the dependencies have been included in this code sample. Thanks for your help!
i dont' know if it is a typo but i see s "," comma at the end of the line where you declare your translator = models.ForeignKey(MachineTranslator)
This is why maybe the attribute is ot seens

Categories

Resources