Code based unique constraint Django Model - python

I have a Django model that looks like this:
class Categories(models.Model):
"""
Model for storing the categories
"""
name = models.CharField(max_length=8)
keywords = models.TextField()
spamwords = models.TextField()
translations = models.TextField()
def save(self, force_insert=False, force_update=False):
"""
Custom save method that converts the name to uppercase
"""
self.name = self.name.upper()
super(Categories, self).save(force_insert, force_update)
Whenever the data is inserted or updated. I'd like to check that that a record with same name doesn't exists. It's a unique constraint that I'd like to implement via code and not the DB. The amount of data in this table is minuscule so the the performance hit is not an issue. If there is an constraint violation, I'd like to raise one of Django's inbuilt constraint exceptions instead of creating a custom one.
Could someone how me the best/fastest way to accomplish this?
Thanks.

In your model definition you can tell Django that 'name' should be unique:
name = models.CharField(max_length=8, unique=True)
A django.db.IntegrityError will be raised if you attempt to save two records with the same name.

in the view
try:
Category.objects.get(name='name')
except Category.DoesNotExist:
# call the save method of model

Related

Django Models (dynamic?)

I am just starting Django Website;
I have deciced to go with a model (with SQLite DB), which have the following properties:
class Flow(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE,
verbose_name="Owner", default="ADMIN")
source = models.CharField(default='HTTP', choices=SOURCE_CHOICES, editable=True, max_length=12)
name = models.CharField(max_length=50, default=" ")
date = models.DateTimeField(verbose_name="Creation date")
I want to add others fields to this model depending on the source field value.
For example if the source field : 'File' is selected. I will create additionnal field (like file name, file directory ...) If 'Http' is selected, I will create a field URL.
Thus depending on the source field, I will have differents field and type.
I have read this kind of model is difficult to reprensent in Django; I am open minded to other kind of solution. My idea was to created as many model as the possible source value.
Even if you can create dynamic field in Django, you can't create dynamic column in Sqlite table FLOW.
If you plan to use same types of fields in different cases, you can create field with abstract name, for example path. That can be as URL as local file path.
In common way you need to create all columns for all choices for DB table.
class Flow(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE,
verbose_name="Owner", default="ADMIN")
source = models.CharField(default='HTTP', choices=SOURCE_CHOICES, editable=True, max_length=12)
name = models.CharField(max_length=50, default=" ")
date = models.DateTimeField(verbose_name="Creation date")
local_path = models.ImageField(upload_to='files', null=True, blank=True) # when source is file
url = models.URLField(null=True, blank=True) # when source is http
So local_path may be empty when you using HTTP source.
Inside view you can dynamically get (or set) value through serializer:
class FlowSerializer(serializers.ModelSerializer):
path = serializers.SerializerMethodField(method_name='get_path_value')
class Meta:
model = Flow
fields = ('owner', 'source', 'name', 'date', 'path')
def get_path_value(self, instance):
if instance.source == 'HTTP':
return instance.url
else:
return instance.local_path
So path will be different for different sources.
And maybe you will be need to install django rest framework for this solution.
EDIT1:
answering to question
So if I understand well, the best pratices should be to create 'blank'
columns
You definitely must to describe all columns in table (unless you using non Sql-like DB, such as MongoDB). So yes, create 'blank' columns, is only one possible way.
But you can override save method in model, for dynamically save fields:
class Flow(models.Model):
temp_path = None
path = models...
url = models...
choice = models...
def save(self, *args, **kwargs):
if choice == 'HTTP':
self.url = temp_path
else:
self.path = temp_path
super().save(*args, **kwargs)
Code above is just a quick idea. Not really working code.
You could implement a custom model field type for that, it helps you save a union object in one column in database. The raw type in database could be a JSON string or other serialized types, for the model field's usage, it's just a python native object!
Here is a piece of sample code: the main jobs are these two methods:
class HandField(models.Field):
# ...
def from_db_value(self, value, expression, connection):
if value is None:
return value
return parse_hand(value)
def to_python(self, value):
if isinstance(value, Hand):
return value
if value is None:
return value
return parse_hand(value)
Check the official docs for details.

How to change field in ModelForm generated html form?

I'm making one of my first django apps with sqlite database. I have some models like for example:
class Connection(models.Model):
routeID = models.ForeignKey(Route, on_delete=models.CASCADE)
activityStatus = models.BooleanField()
car = models.ForeignKey(Car, on_delete=models.CASCADE)
class Route(models.Model):
name = models.CharField(max_length=20)
and forms
class RouteForm(ModelForm):
class Meta:
model = Route
fields = ['name']
class ConnectionForm(ModelForm):
class Meta:
model = Connection
fields = ['routeID', 'activityStatus', 'car']
And in my website, in the url for adding new Connection, I have cascade list containing RouteIDs. And I'd like it to contain RouteName, not ID, so it would be easier to choose. How should I change my ConnectionForm, so I could still use foreign key to Route table, but see RouteName instead of RouteID?
For now it's looking like this, but I'd love to have list of RouteNames, while still adding to Connection table good foreign key, RouteID
Update the Route Model's __str__ method:
class Route(models.Model):
name = models.CharField(max_length=20)
def __str__(self):
return self.name
Because the __str__() method is called whenever you call str() on an object. Django uses str(obj) in a number of places like in Modelform. By default it returns id or pk that is why you were seeing ids in model form. So by overriding it with name, you will see the names appear in choice field. Please see the documentation for more details on this.

How can I resolve custom fields for django models using django_graphene?

Looking at graphene_django, I see they have a bunch of resolvers picking up django model fields mapping them to graphene types.
I have a subclass of JSONField I'd also like to be picked up.
:
# models
class Recipe(models.Model):
name = models.CharField(max_length=100)
instructions = models.TextField()
ingredients = models.ManyToManyField(
Ingredient, related_name='recipes'
)
custom_field = JSONFieldSubclass(....)
# schema
class RecipeType(DjangoObjectType):
class Meta:
model = Recipe
custom_field = ???
I know I could write a separate field and resolver pair for a Query, but I'd prefer it to be available as part of the schema for that model.
What I realize I could do:
class RecipeQuery:
custom_field = graphene.JSONString(id=graphene.ID(required=True))
def resolve_custom_field(self, info, **kwargs):
id = kwargs.get('id')
instance = get_item_by_id(id)
return instance.custom_field.to_json()
But -- this means a separate round trip, to get the id then get the custom_field for that item, right?
Is there a way I could have it seen as part of the RecipeType schema?
Ok, I can get it working by using:
# schema
class RecipeType(DjangoObjectType):
class Meta:
model = Recipe
custom_field = graphene.JSONString(resolver=lambda my_obj, resolve_obj: my_obj.custom_field.to_json())
(the custom_field has a to_json method)
I figured it out without deeply figuring out what is happening in this map between graphene types and the django model field types.
It's based on this:
https://docs.graphene-python.org/en/latest/types/objecttypes/#resolvers
Same function name, but parameterized differently.

Django - validate unique for a calculated field in the Model and also in the ModelForm

TL;DR both my model and my form calculate the value of the field number_as_char. Can I avoid the double work, but still check uniqueness when using the model without the form?
I use Python 3 and Django 1.11
My model looks as follows:
class Account(models.Model):
parent_account = models.ForeignKey(
to='self',
on_delete=models.PROTECT,
null=True,
blank=True)
number_suffix = models.PositiveIntegerField()
number_as_char = models.CharField(
max_length=100,
blank=True,
default='',
unique=True)
#classmethod
def get_number_as_char(cls, parent_account, number_suffix):
# iterate over all parents
suffix_list = [str(number_suffix), ]
parent = parent_account
while parent is not None:
suffix_list.insert(0, str(parent.number_suffix))
parent = parent.parent_account
return '-'.join(suffix_list)
def save(self, *args, **kwargs):
self.number_as_char = self.get_number_as_char(
self.parent_account, self.number_suffix)
super().save(*args, **kwargs)
The field number_as_char is not supposed to be set by the user because it is calculated based on the selected parent_account: it is obtained by chaining the values of the field number_suffix of all the parent accounts and the current instance.
Here is an example with three accounts:
ac1 = Account()
ac1.parent_account = None
ac1.number_suffix = 2
ac1.save()
# ac1.number_as_char is '2'
ac2 = Account()
ac2.parent_account = ac1
ac2.number_suffix = 5
ac2.save()
# ac2.number_as_char is '2-5'
ac3 = Account()
ac3.parent_account = ac2
ac3.number_suffix = 1
ac3.save()
# ac3.number_as_char is '2-5-1'
It is NOT an option to drop the field and use a model property instead, because I need to ensure uniqueness and also use that field for sorting querysets with order_by().
My form looks as follows:
class AccountForm(forms.ModelForm):
class Meta:
model = Account
fields = [
'parent_account', 'number_suffix', 'number_as_char',
]
widgets = {
'number_as_char': forms.TextInput(attrs={'readonly': True}),
}
def clean(self):
super().clean()
self.cleaned_data['number_as_char'] = self.instance.get_number_as_char(
self.cleaned_data['parent_account'], self.cleaned_data['number_suffix'])
I included number_as_char in the form with widget attribute readonly and I use the forms clean() method to calculate number_as_char (it has to be calculated before validating uniqueness).
This all works (the model and the form), but after validating the form, the value of number_as_char will be calculated again by the models save() method. Its not a big problem, but is there a way to avoid this double calculation?
If I remove the calculation from the forms clean() method, then the uniqueness will not be validated with the new value (it will only check the old value).
I don't want to remove the calculation entirely from the model because I use the model in other parts without the form.
Do you have any suggestions what could be done differently to avoid double calculation of the field?
I can't see any way around doing this in two places (save() and clean()) given that you need it to work for non-form-based saves as well).
However I can offer two efficiency improvements to your get_number_as_char method:
Make it a cached_property so that the second time it is called, you simply return a cached value and eliminate double-calculation. Obviously you need to be careful that this isn't called before an instance is updated, otherwise the old number_as_char will be cached. This should be fine as long as get_number_as_char() is only called during a save/clean.
Based on the information you've provided above you shouldn't have to iterate over all the ancestors, but can simply take the number_as_char for the parent and append to it.
The following incorporates both:
#cached_property
def get_number_as_char(self, parent_account, number_suffix):
number_as_char = str(number_suffix)
if parent_account is not None:
number_as_char = '{}-{}'.format(parent_account.number_as_char, number_as_char)
return number_as_char
To be sure that the caching doesn't cause problems you could just clear the cached value after you're done saving:
def save(self, *args, **kwargs):
self.number_as_char = self.get_number_as_char(
self.parent_account, self.number_suffix)
super().save(*args, **kwargs)
# Clear the cache, in case something edits this object again.
del self.get_number_as_char
I tinkered with it a bit, and I think I found a better way.
By using the disabled property on the number_as_char field of your model form, you can entirely ignore users input (and make the field disabled in a single step).
Your model already calculates the number_as_char attribute in the save method. However, if the Unique constraint fails, then your admin UI will throw a 500 error. However, you can move your field calculation to the clean() method, leaving the save() method as it is.
So the full example will look similar to this:
The form:
class AccountForm(forms.ModelForm):
class Meta:
model = Account
fields = [
'parent_account', 'number_suffix', 'number_as_char',
]
widgets = {
'number_as_char': forms.TextInput(attrs={'disabled': True}),
}
The model:
class Account(models.Model):
# ...
def clean(self):
self.number_as_char = self.get_number_as_char(
self.parent_account, self.number_suffix
)
super().clean()
That way anything that generates form based on your model will throw a nice validation error (provided that it uses the built-in model validation, which is the case for Model Forms).
The only downside to this is that if you save a model that triggers the validation error, you will see an empty field instead of the value that failed the validation - but I guess there is some nice way to fix this as well - I'll edit my answer if I also find a solution to this.
After reading all the answers and doing some more digging through the docs, I ended up using the following:
#samu suggested using the models clean() method and #Laurent S suggested using unique_together for (parent_account, number_suffix). Since only using unique_together doesn't work for me because parent_account can be null, I opted for combining the two ideas: checking for existing (parent_account, number_suffix) combinations in the models clean() method.
As a consecuence, I removed number_as_char from the form and it is now only calculated in the save() method. By the way: thanks to #solarissmoke for suggesting to calculated it based on the first parent only, not iterating all the way to the top of the chain.
Another consecuence is that I now need to explicitly call the models full_clean() method to validate uniqueness when using the model without the form (otherwise I will get the database IntegrityError), but I can live with that.
So, now my model looks like this:
class Account(models.Model):
parent_account = models.ForeignKey(
to='self',
on_delete=models.PROTECT,
null=True,
blank=True)
number_suffix = models.PositiveIntegerField()
number_as_char = models.CharField(
max_length=100,
default='0',
unique=True)
def save(self, *args, **kwargs):
if self.parent_account is not None:
self.number_as_char = '{}-{}'.format(
self.parent_account.number_as_char,
self.number_suffix)
else:
self.number_as_char = str(self.number_suffix)
super().save(*args, **kwargs)
def clean(self):
qs = self._meta.model.objects.exclude(pk=self.pk)
qs = qs.filter(
parent_account=self.parent_account,
number_suffix=self.number_suffix)
if qs.exists():
raise ValidationError('... some message ...')
And my form ends up like this:
class AccountForm(forms.ModelForm):
class Meta:
model = Account
fields = [
'parent_account', 'number_suffix',
]
EDIT
I'll mark my own answer as accepted, because non of the suggestions fully suited my needs.
However, the bounty goes to #samus answer for pointing me in the right direction with using the clean() method.
Another way - probably not as good though - would be to use Django signals. You could make a pre_save signal that would set the correct value for number_as_char field on the instance that's about to get saved.
That way you don't have to have it done in a save() method of your model, OR in the clean() method of your ModelForm.
Using signals should ensure that any operation that uses the ORM to manipulate your data (which, by extend, should mean all ModelForms as well) will trigger your signal.
The disadvantage to this approach is that it is not clear from the code directly how is this property generated. One has to stumble upon the signal definition in order to discover that it's even there. If you can live with it though, I'd go with signals.

Restricting ForeignKey choices on sub-classes

I have a set of models, regarding restaurants and the chefs that run them*:
class Chef(models.Model):
name = models.TextField()
class Restaurant(models.Model):
name = models.TextField()
chef = models.ForeignKey(Chef)
class FrenchChef(Chef):
angryness = models.PositiveIntegerField()
class FrenchRestaurant(Restaurant):
region = models.TextField()
Unfortunately, this current model means a non-FrenchChef can run a FrenchRestaurant.
Is there away that I can restrict the queryset for the ForeignKey of a subbclassed model to be a subset of those available on the parent class?
* My modelling isn't actually chefs and restaurants, but this is easier to explain. It might not seem obvious, but Chefs and FrenchChefs do need to be modelled differently.
You could try defining clean method if you're concerned about that
class FrenchRestaurant(models.Model):
# ...
def clean(self):
if not isinstance(self.chief, FrenchChief):
raise ValidationError()
By doing this:
class FrenchChef(Chef):
angryness = models.PositiveIntegerField()
you are creating one more table in database besides Chef. Read about types of model inheritance here: https://docs.djangoproject.com/en/1.7/topics/db/models/#model-inheritance
I think you should create one table for chefs and one table for restaurants, no inheritance needed here:
class Chef(models.Model):
name = models.TextField()
# all chefs with not null angryness is frenchchefs...
# but you can add some field to explicitly save chef type
angryness = models.PositiveIntegerField(null=True)
class Restaurant(models.Model):
name = models.TextField()
chef = models.ForeignKey(Chef)
region = models.TextField()
# rtype added here but it is not necessarily
rtype = models.IntegerField(choices=restaurans_types)
And restriction (filtering) of choices should be in forms:
class FrenchRestaurantForm(forms.ModelForm):
def __init__(self, *args,**kwargs):
super (FrenchRestaurantForm, self ).__init__(*args,**kwargs)
self.fields['chef'].queryset = Chef.objects.filter(
angryness__gte=MIN_ANGRYNESS_LVL)
def save(commit=True):
model = super(FrenchRestaurantForm, self).save(commit=False)
model.rtype = SomeFrenchRestTypeConst
if commit:
model.save()
return model
class Meta:
model = Restaurant
To check user input you can add clean method to form field https://docs.djangoproject.com/en/1.7/ref/forms/validation/#cleaning-a-specific-field-attribute
If FrenchChef was created intentionally (it is a different table in database), then you should add it to FrenchRestaurant (another table -> another fk id):
class FrenchRestaurant(Restaurant):
region = models.TextField()
frenchchef = models.ForeignKey(FrenchChef)
Like I was mentioning in the comment, You can look at django model data validation methods. Just found another note at this post.
Adding Custom Django Model Validation
Below is a common pattern followed to do validation. code snippet is extract from one of the answers in the abouve mentioned post . answered by https://stackoverflow.com/users/247542/cerin
class BaseModel(models.Model):
def clean(self, *args, **kwargs):
# add custom validation here
super(BaseModel, self).clean(*args, **kwargs)
def save(self, *args, **kwargs):
self.full_clean()
super(BaseModel, self).save(*args, **kwargs)
You can go ahead and read more about validation in django documentation.
If you are looking for any type/inheretance based solutions that might exist. I am not sure if they might exist. I would still like to see if someone comes up with such provision in django.

Categories