Django one of 2 fields must not be null - python

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.

Related

Model instances without primary key value are unhashable with Djongo

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)

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)

Having trouble wrapping my head around follower/target models in Models.py

I have just started with making a similar site to Pinterest and the site has follower/target system that I have barely any understanding of. So far, my models.py code is below:
from django.db import models
class User(models.Model):
username = models.CharField(max_length=45, null=True)
email = models.CharField(max_length=200, null=True)
password = models.CharField(max_length=200)
nickname = models.CharField(max_length=45, null=True)
target = models.ManyToManyField(self, through='Follow')
follower = models.ManyToManyField(self, through='Follow')
class Meta:
db_table = 'users'
class Follow(models.Model):
follower = models.ForeignKey(User, on_delete=models.CASCADE, related_name='targets')
target = models.ForeignKey(User, on_delete=models.CASCADE, related_name='followers')
class Meta:
db_table = 'follows'
This code was made with reference to another StackOverflow thread
Django models: database design for user and follower
However, I am having trouble understanding how using "related_name='targets' in 'follower' and "related_name='followers'" in 'target' where I can't see any 'targets'(plural) or 'followers'(plural) in other areas of models.py
Should I get rid of that related_name, since there is no such table called "followers" or "targets"? And if you spot major errors in my code or logic, can you tell me? Thanks!
Should I get rid of that related_name, since there is no such table called followers or targets.
There is never a table named followers or targets. The related_name [Django-doc] is a conceptual relation Django makes to the other model (in this case User). It means that for a User object myuser, you can access the Follow objects that refer to that user through target for example with myuser.followers.all(), so:
Follow.objects.filter(target=myuser)
is equivalent to:
myuser.followers.all()
The default of a related_name is modelname_set, so here that would be follow_set. But if you remove both related_names, then that would result in a name conflict, since one can not add two relations follow_set to the User model (and each having a different semantical value).
if you spot major errors in my code or logic, can you tell me?
The problem is that since ManyToManyFields refer to 'self' (it should be 'self' as string literal), it is ambigous what the "source" and what the target will be, furthermore Django will assume that the relation is symmetrical [Django-doc], which is not the case. You should specify what the source and target foreign keys are, you can do that with the through_fields=… parameter [Django-doc]. It furthermore is better to simply define the related_name of the ManyToManyField in reverse, to avoid duplicated logic.
from django.db import models
class User(models.Model):
username = models.CharField(max_length=45, unique=True)
email = models.CharField(max_length=200)
password = models.CharField(max_length=200)
nickname = models.CharField(max_length=45)
follows = models.ManyToManyField(
'self',
through='Follow',
symmetrical=False,
related_name='followed_by',
through_fields=('follower', 'target')
)
class Meta:
db_table = 'users'
class Follow(models.Model):
follower = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='targets'
)
target = models.ForeignKey(
User,
on_delete=models.CASCADE,
related_name='followers'
)
class Meta:
db_table = 'follows'
Here a User object myuser can thus access myuser.follows.all() to access all the users that they follow, myuser.followed_by.all() is the set of Users that follow myuser. myuser.targets.all() is the set of Follow objects that he is following, and myuser.followers.all() is the set of Follow objects that are following that user.

Django: How to follow ForeignKey in unique_together constraint?

I have three Django Models:
a Property Model, most importantly having a slug field;
a Collection Model; and
a PropertyCollectionRelationship Model.
The last model models a ManyToMany relationship between the other two and is used as an explicit through of a ManyToManyField.
from django.db import models
class Property(models.Model):
slug = models.SlugField()
collections = models.ManyToManyField('Collection', blank=True, through='PropertyCollectionRelationship')
# ... other unimportant stuff omitted ...
class Collection(models.Model):
pass
# ... lots of stuff omitted ...
class PropertyCollectionRelationship(models.Model):
prop = models.ForeignKey(Property, on_delete=models.CASCADE)
coll = models.ForeignKey(Collection, on_delete=models.CASCADE)
I would like to add a uniqueness constraint which ensures that each collection has at most one property with any given slug. I tried to express this via a unique_together option in the Model Meta:
class PropertyCollectionRelationship(models.Model):
class Meta:
unique_together = [['coll', 'prop__slug']]
prop = models.ForeignKey(Property, on_delete=models.CASCADE)
coll = models.ForeignKey(Collection, on_delete=models.CASCADE)
This resulted in the following System Check Error:
SystemCheckError: System check identified some issues:
ERRORS:
myapp.PropertyCollectionRelationship: (models.E012) 'unique_together' refers to the nonexistent field 'prop__slug'.
How, if at all, can I achieve such a constraint?
Edit: As suggested below I could set unique=True on the slug field, however my use case requires different properties with the same slug to coexist.

Using a class as a part of another class in django models

Assume I am writing an app to change configurations in a machine. I have 3 created tables as below. Machine configuration shows the current state of configurations for our machine. Users can create their tickets and request for changes of the configurations. RequestDetails will be the table to keep the proposed cofigurations plus some extra info such as the name of the requestor, date etc.
These classes are just a simple examples, in the real model I would have nearly 600+ fields=configuration presented in class MachineConfiguration. I should have EXACTLY THE SAME fields in RequestDetails class too. I was wondering there is a way NOT TO REPEAT MYSELF when defining class RequestDetails when it comes to all the fields that exist in MachineConfiguration model?
I want to write it in a way that if I changed anything in MachineConfiguration table, the same change would apply to RequestDetails table too.
Thanks in advance for the help.
class RequestTicket(models.Model):
subject=models.CharField(max_length=50, null=False, blank=False)
description=models.TextField(null=False, blank=True)
class MachineConfiguration(models.Model):
field_1 = models.CharField(null=False,blank=True)
field_2 = models.CharField(null=False, blank=True)
field_3 = models.CharField(null=False, blank=True)
class RequestDetails(models.Model):
tracking_number=models.ForeignKey('RequestTicket')
field_A=models.DateField(null=True, blank=False)
field_B=models.TextField(null=True, blank=False)
field_1 = models.CharField(null=False, blank=True)
field_2 = models.CharField(null=False, blank=True)
field_3 = models.CharField(null=False, blank=True)
Yes you can create the base class and inherit that class in another class like,
class BaseModel(models.Model):
field1 = models.CharField()
field2 = models.CharField()
class Meta:
abstract = True
And inherit this class in another model to get those same field,
# Now if you change any field in BaseModel, it will reflect in both the models
class MachineConfiguration(BaseModel):
pass
class RequestDetails(BaseModel):
field3 = models.CharField()

Categories

Resources