Django unique_together doesn't work with ForeignKey=None - django

I saw some ppl had this problem before me, but on older versions of Django, and I'm running on 1.2.1.
I have a model that looks like:
class Category(models.Model):
objects = CategoryManager()
name = models.CharField(max_length=30, blank=False, null=False)
parent = models.ForeignKey('self', null=True, blank=True, help_text=_('The direct parent category.'))
class Meta:
unique_together = ('name', 'parent')
Whenever i try to save in the admin a category with a parent set to None, it still works when there's another category with the SAME name and parent set to None.
Ideas on how to solve this gracefully?

The unique together constraint is enforced at the database level, and it appears that your database engine does not apply the constraint for null values.
In Django 1.2, you can define a clean method for your model to provide custom validation. In your case, you need something that checks for other categories with the same name whenever the parent is None.
class Category(models.Model):
...
def clean(self):
"""
Checks that we do not create multiple categories with
no parent and the same name.
"""
from django.core.exceptions import ValidationError
if self.parent is None and Category.objects.filter(name=self.name, parent=None).exists():
raise ValidationError("Another Category with name=%s and no parent already exists" % self.name)
If you are editing categories through the Django admin, the clean method will be called automatically. In your own views, you must call category.fullclean().

I had that problem too and solved it by creating a supermodel with clean method (like Alasdair suggested) and use it as base class for all my models:
class Base_model(models.Model):
class Meta:
abstract=True
def clean(self):
"""
Check for instances with null values in unique_together fields.
"""
from django.core.exceptions import ValidationError
super(Base_model, self).clean()
for field_tuple in self._meta.unique_together[:]:
unique_filter = {}
unique_fields = []
null_found = False
for field_name in field_tuple:
field_value = getattr(self, field_name)
if getattr(self, field_name) is None:
unique_filter['%s__isnull'%field_name] = True
null_found = True
else:
unique_filter['%s'%field_name] = field_value
unique_fields.append(field_name)
if null_found:
unique_queryset = self.__class__.objects.filter(**unique_filter)
if self.pk:
unique_queryset = unique_queryset.exclude(pk=self.pk)
if unique_queryset.exists():
msg = self.unique_error_message(self.__class__, tuple(unique_fields))
raise ValidationError(msg)

Unfortunately, for those of us using PostgreSQL as our backend database engine, there will never have a fix for this issue:
"Currently, only B-tree indexes can be declared unique.
When an index is declared unique, multiple table rows with equal indexed values are not allowed. Null values are not considered equal. A multicolumn unique index will only reject cases where all indexed columns are equal in multiple rows.
PostgreSQL automatically creates a unique index when a unique constraint or primary key is defined for a table. The index covers the columns that make up the primary key or unique constraint (a multicolumn index, if appropriate), and is the mechanism that enforces the constraint."
Source: https://www.postgresql.org/docs/9.0/indexes-unique.html

Related

How to convert field values of a model into field names of a new one (model) in DJANGO 4?

I'm building models for a project in DJANGO 4, but I got stuck when it came to CONVERTING FIELD VALUES OF AN EXISTING MODEL INTO THE FIELD NAMES OF A NEW ONE (MODEL).
As may be seen from the image I do attach, I have MODEL A with 'fieldName_3'. I'd appreciate a help to convert the values of this field into the field names of the MODEL B.
Descriptive Image
My advancing thanks!
from django.db import models
CHOICES = (('option1','option1'),('option2','option2'))
class Model_A(models.Model):
fieldName_1 = models.CharField(max_length=50)
fieldName_2 = models.CharField(max_length=20)
fieldName_3 = models.CharField(max_length=3)
class Model_B(models.Model):
def values_from_fildName3():
values = Model_A.fieldName_3
for value in values:
value = models.CharField(max_length=4, choices=CHOICES)
yield value
With the code above I get the error "TypeError: 'DeferredAttribute' object is not iterable"
Try This correction in a more modern approach based on the code you showed above.
from django.core.exceptions import ValidationError
from django.db import models
CHOICES = (("option1", "Option 1"), ("option2", "Option 2"))
class Model_A(models.Model):
field_name_1 = models.CharField(max_length=50)
field_name_2 = models.CharField(max_length=20)
field_name_3 = models.CharField(max_length=3)
class Model_B(models.Model):
field_name_3_choices = models.CharField(max_length=4, validators=[])
def clean(self):
super().clean()
field_name_3_values = (
obj.field_name_3
for obj in Model_A.objects.only("field_name_3").distinct()
)
if self.field_name_3_choices not in field_name_3_values:
raise ValidationError(
f"{self.field_name_3_choices} is not a valid value for field_name_3_choices."
)
def save(self, *args, **kwargs):
self.full_clean()
super().save(*args, **kwargs)
In this code, the staticmethod decorator has been removed from values from field name 3() and replaced with an empty list as the initial value of the validators parameter for the field name 3 choices field in Model B. This instructs Django to use no validators by default for the field.
I've also defined a custom clean() method for Model B that calls the parent clean() method and then determines if the value of field name 3 choices is among the list of unique values for field name 3 in Model A. If the value is invalid, the ValidationError exception is thrown.
Lastly, I've overridden the save() method to call full clean() prior to calling the parent method save(). This ensures the custom validation is performed prior to saving the object.
This method should allow you to validate the field name 3 choices options using Model A's values without having to define a separate staticmethod.

How to enforce different values in multiple ForeignKey fields for Django

I have the following models in Django:
from django.db import models
class Team(models.Model):
name = models.CharField(max_length=200)
class Match(models.Model):
team_home = models.ForeignKey(Team)
team_visitors = models.ForeignKey(Team)
league = models.CharField(max_length=200)
date_played = models.DateField()
The idea is to be able to have a 'Match' object which has two teams who played a match of some game. It would be very odd that a team be playing itself. How can I guarantee that team_home is not equal to team_visitors?
You can use this CheckConstraint in class Meta of your django model:
class Meta:
constraints = [
models.CheckConstraint(
check=~Q(team_home=F('team_visitors')),
name='team_home_and_team_visitors_can_not_be_equal')
]
This cannot be done through pure Django. There's a ticket for adding CHECK constraints: https://code.djangoproject.com/ticket/11964
To ensure the situation where team_home == team_visitors never happens, you will need to add a custom constraint to the table which is database dependent. As an example, in MySQL and PostgresQL:
alter table myapp_match add constraint match_teams_not_equal check (team_home_id <> team_visitors_id);
This will cause the database to raise an integrity error when a save happens. Note that there are also databases where you cannot implement such a constraint.
You can partially ensure that team_home != team_visitors by overriding the save method:
class Match(models.Model):
....
def save(self, *args, **kwargs):
if self.team_home == self.team_visitors:
raise Exception('attempted to create a match object where team_home == team_visitors')
super(Match, self).save(*args, **kwargs)
However, someone can modify the database directly, or use update queryset in Django and you can still end up with Match objects where team_home == team_visitor.
I believe you can use the unique_together property of the Meta class: unique_together
class Match(models.Model):
...
class Meta:
unique_together = ("team_home", "team_visitors")

django-rest-framework - trying to set required=False flag on nested 1-to-M?

I'm having some issue with django-rest-framework, and nested objects.
I have a Cart object, as well as CartItem, which links back to a Cart:
class Cart(models.Model):
customer = models.ForeignKey(Customer)
date_created = models.DateTimeField(auto_now_add=True)
date_modified = models.DateTimeField(auto_now=True)
class CartItem(models.Model):
cart = models.ForeignKey(Cart, related_name='cartitems')
product = models.ForeignKey(Product, help_text='Product in a cart')
quantity = models.PositiveIntegerField(default=1, help_text='Quantity of this product.')
date_added = models.DateTimeField(auto_now_add=True, help_text='Date that this product was added to the cart.')
I've created serializers for both:
class CartItemSerializer(serializers.ModelSerializer):
product = serializers.HyperlinkedRelatedField(view_name='product-detail')
class Meta:
model = CartItem
class CartSerializer(serializers.ModelSerializer):
customer = serializers.HyperlinkedRelatedField(view_name='customer-detail')
cartitems = CartItemSerializer(required=False)
total_price = serializers.CharField(source='total_price', read_only=True)
shipping_cost = serializers.CharField(source='shipping_cost', read_only=True)
class Meta:
model = Cart
fields = ('id', 'customer', 'date_created', 'date_modified', 'cartitems', 'total_price', 'shipping_cost')
However, whenever I try to POST to create a new cart, I get an error, assumedly when it tries to set the non-existent CartItem:
TypeError at /api/v1/carts/
add() argument after * must be a sequence, not NoneType
However, a Cart isn't required to actually have CartItems.
Is there any way to get DRF to respect the required=False flag I get on Cart.cartitems?
Cheers,
Victor
EDIT:
I took a stab at tracing it through again:
It's calling BaseSerializer.save() in rest_framework/serializers.py with a CartSerializer object.
def save(self, **kwargs):
"""
Save the deserialized object and return it.
"""
if isinstance(self.object, list):
[self.save_object(item, **kwargs) for item in self.object]
if self.object._deleted:
[self.delete_object(item) for item in self.object._deleted]
else:
self.save_object(self.object, **kwargs)
return self.object
It then calls save_object() on the same class:
def save_object(self, obj, **kwargs):
"""
Save the deserialized object and return it.
"""
if getattr(obj, '_nested_forward_relations', None):
# Nested relationships need to be saved before we can save the
# parent instance.
for field_name, sub_object in obj._nested_forward_relations.items():
if sub_object:
self.save_object(sub_object)
setattr(obj, field_name, sub_object)
obj.save(**kwargs)
if getattr(obj, '_m2m_data', None):
for accessor_name, object_list in obj._m2m_data.items():
setattr(obj, accessor_name, object_list)
del(obj._m2m_data)
if getattr(obj, '_related_data', None):
for accessor_name, related in obj._related_data.items():
if isinstance(related, RelationsList):
# Nested reverse fk relationship
for related_item in related:
fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name
setattr(related_item, fk_field, obj)
self.save_object(related_item)
# Delete any removed objects
if related._deleted:
[self.delete_object(item) for item in related._deleted]
elif isinstance(related, models.Model):
# Nested reverse one-one relationship
fk_field = obj._meta.get_field_by_name(accessor_name)[0].field.name
setattr(related, fk_field, obj)
self.save_object(related)
else:
# Reverse FK or reverse one-one
setattr(obj, accessor_name, related)
del(obj._related_data)
The Cart object has a _related_data field that is set to a dict:
{'cartitems': None}
Hence, on the second-last line, it calls setattr in django/db/models/fields/related.py:
def __set__(self, instance, value):
if instance is None:
raise AttributeError("Manager must be accessed via instance")
manager = self.__get__(instance)
# If the foreign key can support nulls, then completely clear the related set.
# Otherwise, just move the named objects into the set.
if self.related.field.null:
manager.clear()
manager.add(*value)
It's this last liner (manager.add(*value)) that causes the:
TypeError: add() argument after * must be a sequence, not NoneType
Checking the Serializer Relation Docs, first you need to add many=True to your cartitems field.
Unfortunately this is read-only. The docs just say "For read-write relationships, you should use a flat relational style" — you can find a question about that here (although that's only dealing with the 1-1 case).
Current strategies involve making cartitems read-only and then either: doing something post_save, using a second serializer or making a separate request to a separate endpoint to set the related entities. Given that better support for Nested Writes is coming I'd probably be inclined towards a separate request to a separate endpoint for the moment (though that will obviously depend on your constraints).
I hope that helps.
EDIT: (After update to question & discussion in comments).
If you're using a separate endpoint for adding CartItems then making cartitems read-only should eliminate the error.
However (if you're not making it read-only) looking at the DRF code you posted from save_object it occurs that in the related_item in related block you really do need a list. The appropriate dict (fragment) for a Cart with no CartItems is not {'cartitems': None} but rather {'cartitems': []}. — This of course means your required=False flag isn't doing anything. (So perhaps the short answer is "No" — Will now defer to the mailing list discussion

Django custom update model form - display selected field of related model rather than foreign key id.

My question is: is there a way to create custom model form that will use a specified field from a related model rather than the related model's id?
To clarify, if I have the following two models:
class ModelOne(models.Model):
id = models.AutoField(primary_key = True)
name = models.CharField(unique = True, blank = False, null = False)
class ModelTwo(models.Model):
id = models.AutoField(primary_key = True)
parent = models.ForeignKey(ModelOne, blank = False, null = False)
attribute_1 = models.CharField(blank = False, null = False)
attribute_2 = models.IntegerField(blank = False, null = False)
Now if I create an UpdateView on ModelTwo using a ModelForm then the parent field will be pre-filled with the corresponding id from ModelOne. However I want it to display the name attribute of ModelOne and then on form submission parse the unique name (of ModelOne) to the corresponding instance of ModelOne. The reason I want to do it this way, is that I believe it is far more intuitive from a users perspective to deal with the name of ModelOne (when updating a ModelTwo instance) rather than its "id".
Any suggestions of how I can do this?
Firstly, try defining the unicode method on ModelOne. It might not apply to the solution, but it's worth having - it will drive the text values in a form Select widget...
def __unicode__(self):
'''Warning: be careful not to use related objects here,
could cause unwanted DB hits when debugging/logging
'''
return self.name
If that's not sufficient, something like this might work (it is adapted from a form I have that updates the user's name attached to a profile)...
class M2Form(forms.ModelForm):
m1_name = forms.CharField()
class Meta:
model = ModelTwo
def save(self, *args, **kw):
# Update your name field here, something like
if self.cleaned_data.get('m1_name'):
self.instance.parent = ModelOne.objects.get(name=self.cleaned_data.get('m1_name'))
return super(M2Form, self).save(*args, **kw)
This is untested, and you'll likely need to adapt this to validate that the name exists and make sure the original parent field doesn't appear on the form. With any luck, the first answer covers what I think your question is.
Using Rog's answer as a starting point and delving through some of Django's internals I eventually came to a working solution. Given my level of Django knowledge, I imagine there is a better way of doing this; so if you have another method please add it.
So based on the above two models, I created the following form class:
class CustomForm(forms.ModelForm):
parent = models.CharField(label='Name')
class Meta:
model = ModelTwo
exclude = ['parent']
def __init__(self,*args,**kwargs):
# The line of code below is the one that I was looking for. It pre-populates
# the "parent" field of the form with the "name" attribute of the related
# ModelOne instance.
kwargs['initial']['parent'] = kwargs['instance'].parent.name
super(CustomForm,self).__init__(*args,**kwargs)
# The next line is for convenience and orders the form fields in our desired
# order. I got this tip from:
# http://stackoverflow.com/questions/913589/django-forms-inheritance-and-order-of-form-fields
self.fields.keyOrder = ['parent','attribute_1','attribute_2']
def save(self, *args, **kwargs):
if self.cleaned_data.get('parent'):
# This section of code is important because we need to convert back from the
# unique 'name' attribute of ModelOne to the corresponding instance so that
# ModelTwo can be saved. Thanks goes to Rog for this section of code.
self.instance.parent = ModelOne.objects.get(name=self.cleaned_data.get('parent'))
return super(CustomForm, self).save(*args, **kwargs)

Unique model field in Django and case sensitivity (postgres)

Consider the following situation: -
Suppose my app allows users to create the states / provinces in their
country. Just for clarity, we are considering only ASCII characters
here.
In the US, a user could create the state called "Texas". If this app
is being used internally, let's say the user doesn't care if it is
spelled "texas" or "Texas" or "teXas"
But importantly, the system should prevent creation of "texas" if
"Texas" is already in the database.
If the model is like the following:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
The uniqueness would be case-sensitive in postgres; that is, postgres
would allow the user to create both "texas" and "Texas" as they are
considered unique.
What can be done in this situation to prevent such behavior. How does
one go about providing case-insenstitive uniqueness with Django and
Postgres
Right now I'm doing the following to prevent creation of case-
insensitive duplicates.
class CreateStateForm(forms.ModelForm):
def clean_name(self):
name = self.cleaned_data['name']
try:
State.objects.get(name__iexact=name)
except ObjectDoesNotExist:
return name
raise forms.ValidationError('State already exists.')
class Meta:
model = State
There are a number of cases where I will have to do this check and I'm not keen on having to write similar iexact checks everywhere.
Just wondering if there is a built-in or
better way? Perhaps db_type would help? Maybe some other solution exists?
You could define a custom model field derived from models.CharField.
This field could check for duplicate values, ignoring the case.
Custom fields documentation is here http://docs.djangoproject.com/en/dev/howto/custom-model-fields/
Look at http://code.djangoproject.com/browser/django/trunk/django/db/models/fields/files.py for an example of how to create a custom field by subclassing an existing field.
You could use the citext module of PostgreSQL https://www.postgresql.org/docs/current/static/citext.html
If you use this module, the the custom field could define "db_type" as CITEXT for PostgreSQL databases.
This would lead to case insensitive comparison for unique values in the custom field.
Alternatively you can change the default Query Set Manager to do case insensitive look-ups on the field. In trying to solve a similar problem I came across:
http://djangosnippets.org/snippets/305/
Code pasted here for convenience:
from django.db.models import Manager
from django.db.models.query import QuerySet
class CaseInsensitiveQuerySet(QuerySet):
def _filter_or_exclude(self, mapper, *args, **kwargs):
# 'name' is a field in your Model whose lookups you want case-insensitive by default
if 'name' in kwargs:
kwargs['name__iexact'] = kwargs['name']
del kwargs['name']
return super(CaseInsensitiveQuerySet, self)._filter_or_exclude(mapper, *args, **kwargs)
# custom manager that overrides the initial query set
class TagManager(Manager):
def get_query_set(self):
return CaseInsensitiveQuerySet(self.model)
# and the model itself
class Tag(models.Model):
name = models.CharField(maxlength=50, unique=True, db_index=True)
objects = TagManager()
def __str__(self):
return self.name
a very simple solution:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def clean(self):
self.name = self.name.capitalize()
Explicit steps for Mayuresh's answer:
in postgres do: CREATE EXTENSION citext;
in your models.py add:
from django.db.models import fields
class CaseInsensitiveTextField(fields.TextField):
def db_type(self, connection):
return "citext"
reference: https://github.com/zacharyvoase/django-postgres/blob/master/django_postgres/citext.py
in your model use: name = CaseInsensitiveTextField(unique=True)
On the Postgres side of things, a functional unique index will let you enforce unique values without case. citext is also noted, but this will work with older versions of PostgreSQL and is a useful technique in general.
Example:
# create table foo(bar text);
CREATE TABLE
# create unique index foo_bar on foo(lower(bar));
CREATE INDEX
# insert into foo values ('Texas');
INSERT 0 1
# insert into foo values ('texas');
ERROR: duplicate key value violates unique constraint "foo_bar"
Besides already mentioned option to override save, you can simply store all text in lower case in database and capitalize them on displaying.
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def save(self, force_insert=False, force_update=False):
self.name = self.name.lower()
super(State, self).save(force_insert, force_update)
You can use lookup='iexact' in UniqueValidator on serializer, like this:
class StateSerializer(serializers.ModelSerializer):
name = serializers.CharField(validators=[
UniqueValidator(
queryset=models.State.objects.all(),lookup='iexact'
)]
django version: 1.11.6
If you don't want to use a postgres-specific solution, you can create a unique index on the field with upper() to enforce uniqueness at the database level, then create a custom Field mixin that overrides get_lookup() to convert case-sensitive lookups to their case-insensitive versions. The mixin looks like this:
class CaseInsensitiveFieldMixin:
"""
Field mixin that uses case-insensitive lookup alternatives if they exist.
"""
LOOKUP_CONVERSIONS = {
'exact': 'iexact',
'contains': 'icontains',
'startswith': 'istartswith',
'endswith': 'iendswith',
'regex': 'iregex',
}
def get_lookup(self, lookup_name):
converted = self.LOOKUP_CONVERSIONS.get(lookup_name, lookup_name)
return super().get_lookup(converted)
And you use it like this:
from django.db import models
class CICharField(CaseInsensitiveFieldMixin, models.CharField):
pass
class CIEmailField(CaseInsensitiveFieldMixin, models.EmailField):
pass
class TestModel(models.Model):
name = CICharField(unique=True, max_length=20)
email = CIEmailField(unique=True)
You can read more about this approach here.
You can do this by overwriting the Model's save method - see the docs. You'd basically do something like:
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def save(self, force_insert=False, force_update=False):
if State.objects.get(name__iexact = self.name):
return
else:
super(State, self).save(force_insert, force_update)
Also, I may be wrong about this, but the upcoming model-validation SoC branch will allow us to do this more easily.
Solution from suhail worked for me without the need to enable citext, pretty easy solution only a clean function and instead of capitalize I used upper(). Mayuresh's solution also works but changed the field from CharField to TextField.
class State(models.Model):
name = models.CharField(max_length=50, unique=True)
def clean(self):
self.name = self.name.upper()