Django unique together constraint failure? - python

Using Django 1.5.1. Python 2.7.3.
I wanted to do a unique together constraint with a foreign key field and a slug field. So in my model meta, I did
foreign_key = models.ForeignKey("self", null=True, default=None)
slug = models.SlugField(max_length=40, unique=False)
class Meta:
unique_together = ("foreign_key", "slug")
I even checked the table description in Postgres (9.1) and the constraint was put into the database table.
-- something like
"table_name_foreign_key_id_slug_key" UNIQUE CONSTRAINT, btree (foreign_key_id, slug)
However, I could still save into the database table a foreign_key of None/null and duplicate strings.
For example,
I could input and save
# model objects with slug="python" three times; all three foreign_key(s)
# are None/null because that is their default value
MO(slug="python").save()
MO(slug="python").save()
MO(slug="python").save()
So after using unique_together, why can I still input three of the same valued rows?
I'm just guessing right now that it might have to do with the default value of None for the foreign_key field, because before the unique_together, when I just had unique=True on slug, everything worked fine. So if that is the case, what default value should I have that indicates a null value, but also maintains the unique constraint?

In Postgresql NULL isn't equal to any other NULL. Therefore the rows you create are not the same (from Postgres' perspective).
Update
You have a few ways to deal with it:
Forbid the Null value for foreign key and use some default value
Override the save method of your model to check that no such row exists
Change SQL standard :)

Add a clean method to your model, so you can edit an existing row.
def clean(self):
queryset = MO.objects.exclude(id=self.id).filter(slug=self.slug)
if self.foreign_key is None:
if queryset.exists():
raise ValidationError("A row already exists with this slug and no key")
else:
if queryset.filter(foreign_key=self.foreign_key).exists():
raise ValidationError("This row already exists")
Beware, clean (or full_clean) isn't called by the default save method.
NB: if you put this code in the save method, update forms (like in the admin) won't work: you will have a traceback error due to the ValidationError exception.

Just manually create secondary index on slug field, but only for NULL values in foreign_key_id:
CREATE INDEX table_name_unique_null_foreign_key
ON table_name (slug) WHERE foreign_key_id is NULL
Please note, that Django does not support this, so without custom form/model validation you will get pure IntegrityError / 500.
Possible duplicate of Create unique constraint with null columns

As hobbyte mentioned, "In Postgresql NULL isn't equal to any other NULL. Therefore the rows you create are not the same (from Postgres' perspective)."
Another possible way to address this challenge is to add custom validation at the view level in the form_valid method.
In views.py:
def form_valid(self, form):
--OTHER VALIDATION AND FIELD VALUE ASSIGNMENT LOGIC--
if ModelForm.objects.filter(slug=slug,foreign_key=foreign_key:
form.add_error('field',
forms.ValidationError( _("Validation error message that shows up in your form. "),
code='duplicate_row', ))
return self.form_invalid(form)
This approach is helpful if you are using class based views, especially if you are automatically assigning values to fields that you want to hide from the user.
Pros:
You don't have to create dummy default values in the database
You can still use update forms (see Toff's answer)
Cons:
- This doesn't protect against duplicate rows created directly at the database level.
- If you use Django's admin backend to create new MyModel objects, you'll need to add this same validation logic to your admin form.

Related

What do I set the default value as when adding a field to a model?

I added the field user which is a foreign key to another model called User. This field was added to the model called Bid. However, when I tried to migrate the changes, I got the message:
It is impossible to add a non-nullable field 'user' to bid without specifying a default. This is because the database needs something to populate existing rows. Please select a fix:
1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
2) Quit and manually define a default value in models.py.
Last time, I set it to 'user' and got an error that stated: ValueError: invalid literal for int() with base 10: 'user'.
What should I set the default value as?
models.py:
class Bid(models.Model):
item = models.ForeignKey(Listing, on_delete=models.CASCADE)
price = models.FloatField()
user = models.ForeignKey(User, on_delete=models.CASCADE)
As the error says, you are creating a new field in your table. When creating a new field, existing rows need to be taken into consideration. Safest approach is to set it as null=True and handle the field later.
user = models.ForeignKey(User, on_delete=models.SET_NULL, null=True)
However you may not want a Bid to have a null user. In which case I recommend looking into how to write a custom migration to populate existing rows.
Another note: if the data you have in your table is not needed, you could consider dropping the table and rebuilding it or deleting your migrations and creating them again with manage.py makemigrations - again only if the data you have in your db is not needed.
If you add a relationship via a new foreign key, you have two options
You make the new FK nullable and let it default to NULL (i.e. None in python). This way, legacy entries with unknown relations will have NULL as the FK, i.e. do not know their users.
You manually populate the legacy fields with the appropriate foreign keys to the Bid records. This requires that you have kept that information beforehand.

Assigning Value(s) to ManyToMany Field

I'm trying to assign a value onto a ManyToMany field in the Order class.
def assign_order(request, user_id):
order_id = request.session.get('order_id')
#picking this session from a previous fxn allowing me to call the order.id
order = get_object_or_404(Order, id=order_id)
employee = Employee.objects.filter(user_id=user_id)
#Employee is a profile model leveraging an AbstractUser
order.deliverer.set(employee)
#using .set() for direct assignment of user(Employee) onto ManyToMany field on "Order"
order.status = 'deliverer_assigned'
order.save()
The problem with this schema is that the fxn will always overwrite my ManyToMany field rather than append a new User(Employee profile) onto it.
1. Is there a better way of accomplishing what I need? i.e. Updating a ManyToMany field outside the admin?
2. How do I prevent the overwrite problem in this scenario?
Using
order.deliverer.add(employee)
rather than .set should work.
See the django docs for more detail.

django update_or_create method creates record but with empty values

I am trying use update_or_create method, it's working but the issue which I see is, it's creating an empty record even though the fields are null=False & blank=False.
Any idea about this folks?
If your field is null=False and blank=False that doesn't mean that the constraints are present in the database. Please check if the column in DB cannot be null and/or run migrations to add it to the DB.
Additionally you need to specify non-default values using the defaults argument of get_or_create():
obj, created = Person.objects.get_or_create(first_name='John', last_name='Lennon',
defaults={'birthday': date(1940, 10, 9)})

Error saving django model with OneToOne field - Column specified twice

This question has been asked before, but the answers there do not solve my problem.
I am using a legacy database, nothing can be changed
Here are my django models, with all but the relevant fields stripped off, obviously class meta has Managed=False in my actual code:
class AppCosts(models.Model):
id = models.CharField(primary_key=True)
cost = models.DecimalField()
class AppDefs(models.Model):
id = models.CharField(primary_key=True)
data = models.TextField()
appcost = models.OneToOneField(AppCosts, db_column='id')
class JobHistory(models.Model):
job_name = models.CharField(primary_key=True)
job_application = models.CharField()
appcost = models.OneToOneField(AppCosts, to_field='id', db_column='job_application')
app = models.OneToOneField(AppDefs, to_field='id', db_column='job_application')
The OneToOne fields work fine for querying, and I get the correct result using select_related()
But when I create a new record for the JobHistory table, when I call save(), I get:
DatabaseError: (1110, "Column 'job_application' specified twice")
I am using django 1.4 and I do not quite get how this OneToOneField works. I can't find any example where primary keys are named differently and has this particular semantics
I need the django model that would let me do this SQL:
select job_history.job_name, job_history.job_application, app_costs.cost from job_history, app_costs where job_history.job_application = app_costs.id;
You have defined appcost and app to have the same underlying database column, job_application, which is also the name of another existing field: so three fields share the same column. That makes no sense at all.
OneToOneFields are just foreign keys constrained to a single value on both ends. If you have foreign keys from JobHistory to AppCost and AppDef, then presumably you have actual columns in your database that contain those foreign keys. Those are the values you should be using for db_field for those fields, not "job_application".
Edit I'm glad you said you didn't design this schema, because it is pretty horrible: you won't have any foreign key constraints, for example, which makes referential integrity impossible. But never mind, we can actually achieve what you want, more or less.
There are various issues with that you have, but the main one is that you don't need the separate "job_application" field at all. That is, as I said earlier, the foreign key, so let it be that. Also note it should be an actual foreign key field, not a one-to-one, since there are many histories to one app.
One constraint that we can't achieve easily in Django is to have the same field acting as FK for two tables. But that doesn't really matter, since we can get to AppCosts via AppDefs.
So the models could just look like this:
class AppCosts(models.Model):
app = models.OneToOneField('AppDefs', primary_key=True, db_field='id')
cost = models.DecimalField()
class AppDefs(models.Model):
id = models.CharField(primary_key=True)
data = models.TextField()
class JobHistory(models.Model):
job_name = models.CharField(primary_key=True)
app = models.ForeignKey(AppDefs, db_column='job_application')
Note that I've moved the one-to-one between Costs and Defs onto AppCosts, since it seems to make sense to have the canonical ID in Defs.
Now, given a JobHistory instance, you can do history.app to get the app instance, history.app.cost to get the app cost, and use the history.app_id to get the underlying app ID from the job_application column.
If you wanted to reproduce that SQL output more exactly, something like this would now work:
JobHistory.objects.values_list('job_name', 'app_id', 'app__appcosts__cost')

Can primary key use BigInteger as the AutoField in Django 1.2.4?

It seems that the default primary key is int. Is there anyway to use the big integer for the autofield as the primary key?
I would suggest you use a newer Django. Official Django documentation doesn't go farther back than 1.3 now. And 1.3 is insecure and unsupported. I realize the question was asked over 3 years ago, but since there is still no accepted answer I will give it a shot.
In Django 1.6.5 you can just do this in your model:
class MyModel(models.Model):
id = models.BigIntegerField(unique=True, primary_key=True)
The primary_key=True will override the default id on the model. In use this field auto increments with each new model object. It just works!
There are a couple of ways I can see to implement this. Either way, you have to define your pk field.
First of all, just create your own id field and override the save method.
modelname(models.Model):
# model definition
def save(self):
self.pkfield = nextIntFucntion()
super(modelname, self).save()
The nextIntFunction() is easy enough with a query of objects ordered by id, then get the id+1
I also found this link BigIntegerField and BigAutoField which seems to solve the problem, but I have not tested it myself
I met the same question too.
I have add some code like
User._meta.has_auto_field = True
User._meta.auto_field = id
And I define the id field to BigIntegerField(primary_key=True)
After I use user.Save(), user.id will have its id, don't need I query again.
I think it works, but it is not a beautiful solution, so I still finding a good way.
Since Django 1.10 you can use BigAutoField as described on documentation works exactly as AutoField but it is guaranteed to fit numbers from 1 to 9223372036854775807.
So you can use it like:
class SomeModel(models.Model):
id = models.BigAutoField()
...
You can hack Django and change the default auto-keys to the right values. Check out:
http://code.djangoproject.com/browser/django/trunk/django/db/backends/mysql/creation.py
from django.conf import settings
from django.db.backends.creation import BaseDatabaseCreation
class DatabaseCreation(BaseDatabaseCreation):
# This dictionary maps Field objects to their associated MySQL column
# types, as strings. Column-type strings can contain format strings; they'll
# be interpolated against the values of Field.__dict__ before being output.
# If a column type is set to None, it won't be included in the output.
data_types = {
'AutoField': 'integer AUTO_INCREMENT',
'BooleanField': 'bool',
'CharField': 'varchar(%(max_length)s)',
You can modify this using a patch in your own code:
DatabaseCreation.data_types['AutoField'] = 'bigint AUTO_INCREMENT'
You will also have to patch the AutoField class:
http://code.djangoproject.com/browser/django/trunk/django/db/models/fields/__init__.py
(untested code, good luck)
http://docs.djangoproject.com/en/dev/topics/db/models/
class BigIntegerField([**options])
available option is :
primary_key
If True, this field is the primary key for the model.
And after all you do a south migration:
ALTER TABLE mytable MODIFY COLUMN myid BIGINT(20) NOT NULL AUTO_INCREMENT;
You are right, sorry. The neccessary snippet is here:
http://djangosnippets.org/snippets/1244/
Allows to create bigint (mysql), bigserial (psql), or NUMBER(19) (oracle) fields which have auto-increment set by using the AutoField of django, therefore ensuring that the ID gets updated in the instance when calling its 'save()' method.
If you would only subclass IntegerField to BigIntegerField and use that as your primary key, your model instance you create would not get the id attribute set when calling 'save()', buy instead you would have to query and load the instance from the DB again to get the ID.
These snippets work. Use the BigAutoField class as your primary key on your model and it works seemlessly without any hacking.

Categories

Resources