My table is defined as below.
class Role(models.Model):
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
to_field="email",
db_column="user",
on_delete=models.CASCADE
)
vertical = models.ForeignKey(
Verticals,
to_field="name",
db_column="vertical",
on_delete=models.CASCADE
)
product_domain = models.ForeignKey(
ProductDomains,
to_field="name",
db_column="product_domain",
null=True,
on_delete=models.CASCADE
)
class Meta:
constraints = [
models.UniqueConstraint(fields=[
'user',
'vertical',
'product_domain'
],
name='unique-permissions-per-user')
]
Here are the migrations that are generated
migrations.CreateModel(
name='Role',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('product_domain', models.ForeignKey(db_column='product_domain', null=True, on_delete=django.db.models.deletion.CASCADE, to='verticals.ProductDomains', to_field='name')),
('user', models.ForeignKey(db_column='user', on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL, to_field='email')),
('vertical', models.ForeignKey(db_column='vertical', on_delete=django.db.models.deletion.CASCADE, to='verticals.Verticals', to_field='name')),
],
),
migrations.AddConstraint(
model_name='role',
constraint=models.UniqueConstraint(fields=('user', 'vertical', 'product_domain'), name='unique-permissions-per-user'),
),
Serializers for the Role model is
class RoleSerializer(serializers.ModelSerializer):
class Meta:
model = Role
fields = '__all__'
Here is the interactive console for the same
Link to console image (Unable to add due to less reputation )
Here the UniqueConstraint is not working, why?
I use models.UniqueConstraint in the same project many times but it doesn't work in this case.
My configuration is
Django - 3.0.4
Django Rest Framework - 3.11.0
Database - MySql
Please help and ask if any information is missing out.
Your logs don't show any attempt to insert the same record into the DB twice, just validation.
It appears Django Rest Framework does not import constraints from UniqueConstraint, and only has code to validate with the old Meta.unique_together constraint definition.
There is a tracking issue, ModelSerializer not generating validators for constraints, on the DRF GitHub where you can follow the latest updates on adding support for UniqueConstraint validation.
Related
We are trying to build endpoints to allow users to edit their own profiles from our front end and we've encountered a problem while trying to edit the "logged in user". This issue happens in django admin as well.
All the rest of this post is specifically referring to the "user" in django admin. I have extended the user and built a custom admin.
if we have 3 users, (imagine all three are super users/is_staff for now). Logged in user 1, I can edit users 2 and 3, but when I go to edit user 1 (logged in user), the message says it was updated but the database does not change.
If I then login as user 2 and update user 1, I can update user 1 but not user 2 as the logged in user.
This same behavior happens on our endpoints with request.user. request.user can edit any user except for the logged in user.
CODE
accounts/models.py
class User(AbstractUser):
timezone = models.CharField(max_length=255, blank=True, null=True)
is_customer = models.BooleanField(default=False)
is_agent = models.BooleanField(default=False)
is_carrier = models.BooleanField(default=False)
is_shipper = models.BooleanField(default=False)
is_tracking = models.BooleanField(default=False)
class Meta:
db_table = 'auth_user'
def __str__(self):
return self.first_name
It is defined in settings:
AUTH_USER_MODEL = 'accounts.User'
accounts/admin.py
CustomUser = get_user_model()
class UserInline(admin.StackedInline):
model = User
class AgentInline(admin.StackedInline):
model = Agent
class CustomUserAdmin(UserAdmin):
model = CustomUser
agent_fields = ('timezone', 'is_agent', 'is_customer', 'is_shipper', 'is_carrier', 'is_tracking')
fieldsets = UserAdmin.fieldsets + (
('Agent Info', {'fields': agent_fields}),
)
inlines = [
AgentInline,
]
admin.site.register(CustomUser, CustomUserAdmin)
MIGRATIONS 0001_initial.py
class Migration(migrations.Migration):
initial = True
dependencies = [
('auth', '0009_alter_user_last_name_max_length'),
]
operations = [
migrations.CreateModel(
name='User',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('password', models.CharField(max_length=128, verbose_name='password')),
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')),
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')),
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and #/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')),
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')),
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')),
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')),
('is_customer', models.BooleanField(default=False)),
('is_agent', models.BooleanField(default=False)),
('is_carrier', models.BooleanField(default=False)),
('is_shipper', models.BooleanField(default=False)),
],
options={
'verbose_name': 'user',
'verbose_name_plural': 'users',
'abstract': False,
},
managers=[
('objects', django.contrib.auth.models.UserManager()),
],
),
]
Screenshots of my testing
Here's the output of the UPDATE, you can see that first name is being saved as blank string. So maybe this is form related?
It's likely that when we added the custom user model we did something wrong as I believe it was added after the project creation.
Because of this i'm assuming the logged in user is maybe making a change to the wrong table in the DB. I'm not exactly sure how to identify this though, normally I would shell in but the original auth_user model is disabled from being imported because of our custom model.
Let me know if I can provide anymore context.
UPDATE 1
It looks like the update is actually working, then it's being immediately overwritten by the original data. See this screenshot, this happens on a single update. You can see an UPDATE statement with the last name having a value, then a second UPDATE with the original data.
tl,dr: The update was done properly, but a middleware caused a request.user.save() (or similar), writing back the old values to the db.
Findings from the comments expanded into an answer:
It turns out the update query was executed just as expected, which matches the message "User was changed successfully". Enabling logging of all sql queries allowed to confirm this.
However, right after the correct update query, there was another query resetting the user to its state before. Here, it helps to know that when the form is saved, it does not update the python object accessible via request.user (because it doesn't know that the updated user is request.user). It would only be updated if someone called refresh_from_db on it.
Thus, I suspected something called request.user.save(), which would then store back the outdated state. This would match the queries we observed. But the django admin view should not do that. On the internet, there are some pages on how to get stack traces together with the query log, which should allow to find out where the query is issued from.
However, even without the hassle of enabling the log, the culprit in this case could be identified to be some middleware. This can be easily tested by simply commenting out any (custom) middleware that is not essential.
I think you probably did initial migration before creating CUSTOM_USER
Now you can delete all migration files and can run a fresh migration.
I created a dummy project just to test the new field JSONField of Django but the column doesn't not appear to be created (I am using Postgresql).
class Author(models.Model):
name = models.CharField(max_length=50)
description = models.TextField()
slug = models.SlugField()
details = models.JSONField()
class Meta:
verbose_name = "Author"
def __str__(self):
return self.name
If i go to the database, the column is not created -- screenshot
When i go to the admin view page of the table Author i get the following error -- screenshot
The error in the admin panel is understandable: you cannot view a column that does not exist. Do you guys have the same error? I can't find this error with JSONField anywhere.
Thanks
Note: This is my first post.
EDIT I create all the fields in the same time. Migration file:
# Generated by Django 3.1.3 on 2020-11-20 10:35
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
name='Author',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=50)),
('description', models.TextField()),
('slug', models.SlugField()),
('details', models.JSONField()),
],
options={
'verbose_name': 'Author',
},
),
]
Migrated with sucess:
Migrations for 'blog':
blog/migrations/0001_initial.py
- Create model Author
check that you migration was applied to the right database
I have the following models:
class Contact(models.Model):
email = models.EmailField()
class EventList(models.Model):
event_contacts = models.ManyToManyField(Contact, through=EventMembership)
class EventMembership(models.Model):
event_list = models.ForeignKey(EventList, null=True, on_delete=models.PROTECT)
event_contact = models.ForeignKey(Contact, null=True, blank=False, on_delete=models.PROTECT)
However, when applying migrations for EventMembership on a completely clean database I get the following error:
psycopg2.errors.InvalidForeignKey: there is no unique constraint
matching given keys for referenced table "contacts_contact"
class Migration(migrations.Migration):
initial = True
dependencies = [
('lists', '0001_initial'),
('contacts', '0002_auto_20200308_2253'),
]
operations = [
migrations.CreateModel(
name='EventMembership',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('event_contact', apps.utils.django_multitenant.fields.TenantForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='contacts.Contact')),
('event_list', apps.utils.django_multitenant.fields.ForeignKey(null=True, on_delete=django.db.models.deletion.PROTECT, to='lists.EventList'))
]
]
Table contacts_contact clearly has a unique constraint in id as the primary key.
What could be causing this error? / How do I debug this?
You just need to do it step by step. Now you are trying to create a foreign key relationship with a table that is not in the database yet. So comment everything out except for Contact model, apply migrations, then add EventList etc. If you are relying on the fact that Contact model goes first, well, it doesn't help in this case.
I have an already migrated Django model, which was created this way:
operations = [
migrations.CreateModel(
name='Victim',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('email', models.EmailField(max_length=200)),
('instagram', models.CharField(max_length=254)),
('old_password', models.CharField(blank=True, max_length=200)),
('new_password', models.CharField(blank=True, max_length=200)),
],
),
]
But now, I want to make email and instagram attribute Blank=True, but password fields make Blank=False.
What is the easiest way to do this: delete and recreate the model (data is not important) or is there a possible way to do this?
You can still change your models and run manage.py makemigrations. It will create another migration to execute the required SQL statements to alter your database schema when running manage.py migrate. This is the role of migrations.
I have Django models with many uncompulsory fields Is there an alternative to avoid null=True and blank=True ? Is there something like blank_fields = ('email','image',) and null_fields = ('email', 'image') ?
No, Django does not have any options like blank_fields or null_fields.
You could subclass the fields to create optional versions, e.g. OptionalCharField, but I would recommend against this. It might be less repetitive than using blank=True, null=True repeatedly, but it will be less readable for other Django developers who look at your code. And as Cezar suggests in the comments you shouldn’t usually use null=True for CharField because that means you have two empty values, '' and None.
There is nothing which Django provides by default. These attributes are per field definitions, not something which is Model level. You have definitions as unique_together, index_together which are model level definitions combining different fields.
One approach can be of subclassing the Fields and provide a default definition -
class CustomIntegerField(models.IntegerField):
def __init__(self, *args, **kwargs):
kwargs['blank'] = True
kwargs['null'] = True
super(CustomIntegerField, self).__init__(**kwargs)
class Test(models.Model):
cus = CustomIntegerField(default=0)
Migration:
class Migration(migrations.Migration):
dependencies = [ ]
operations = [
migrations.CreateModel(
name='Test',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('cus', test.models.CustomIntegerField(blank=True, default=0, null=True)),
],
),
]
You can do this for other fields as well.