Django User Editing, AbstractUser - django

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.

Related

How can I make a user out of a model in Django?

I'm currently developing a web-application for managing driving licenses as a web development homework assignment. Instead of just storing information on every driver as a model I want to make a user for each driver. What is the easiest way to do so? What am I doing wrong?
I updated a car owner model so that it is now inherited from djangdo Abstract User.
# models.py
class Car_owner(AbstractUser):
id_owner = models.IntegerField(primary_key=True)
last_name = models.CharField(max_length=30, null=False)
first_name = models.CharField(max_length=30, null=False)
birth_date = models.DateField(null=True)
passport = models.IntegerField(null=True, blank=True)
address = models.CharField(max_length=50, null=True, blank=True)
nationality = models.CharField(max_length=15, null=True, blank=True)
# username = models.CharField(max_length=16, unique=True, default=uuid4)
# password = models.CharField(max_length=16, default='password')
I also have forms.py file:
from django import forms
from project_first_app.models import Car_owner
class CreateOwner(forms.ModelForm):
class Meta:
model = Car_owner
fields = ['id_owner', 'last_name', 'first_name', 'birth_date', 'passport', 'address', 'nationality']
But when migrating I get the following issue:
UNIQUE constraint failed: new__project_first_app_car_owner.username
In migrations files I have:
migrations.AddField(
model_name='car_owner',
name='username',
field=models.CharField(default=uuid.uuid4, max_length=16, unique=True),
),
and then:
migrations.AlterField(
model_name='car_owner',
name='username',
field=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'),
),
Those are automatically generated.
You have commented username and password in your model, why ?
Password field already exists in the abstractbaseuser, so comment it does not remove it. The abstract model user of Django need a unique information for authenticating user. You can use a Username or an Email.
For using the email instead of username, you can define USERNAME_FIELD = "email" in your class. But you have to define a field email, or you keep username field and add it to fields ModelForm.
There is a constraint on the username field (or email field), so you have to define it for each Car_owner you want to create (it is for authenticating purpose).
your field id_owner is useless, Django create automatically a field called id in each model which is the primary key.
so for resolving your problem easily, add username in fields list of your Form for beginning.

Implement Django REST TokenAuthentication for Multiple User Model

I need to implement TokenAuthentication using Custom user model Consumer & Merchant (The default User model still exists and is used for admin login).
I've been looking on official DRF documentation and days looking on google, but I can't find any detailed reference about how to achieve this, all of the references I found using default User or extended User models.
class Consumer(models.Model):
consumer_id = models.AutoField(primary_key=True)
token = models.UUIDField(default=uuid.uuid4)
email = models.CharField(max_length=100, unique=True, null=True)
password = models.CharField(max_length=500, default=uuid.uuid4)
class Merchant(models.Model):
merchant_id = models.AutoField(primary_key=True)
token = models.UUIDField(default=uuid.uuid4)
email = models.CharField(max_length=100, unique=True)
name = models.CharField(max_length=100)
password = models.CharField(max_length=500, default=uuid.uuid4)
Settings.py
INSTALLED_APPS = [
...
'rest_framework',
...
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': [
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
],
'DEFAULT_PARSER_CLASSES': [
'rest_framework.parsers.JSONParser'
]
}
I'm also using #api_view decorator with function-based views:
#api_view(['POST'])
#renderer_classes([JSONRenderer])
def inbound_product(request, pid):
product = MerchantProduct.objects.get(product_id=pid)
It's recommended to keep authentication data only on one table even if you have multiple user profile tables.So in you case I think you need to have another table for authenticating the users, the table should implement AbstractBaseUser.
And there should be a OneToOne reference between the merchant and customer tables to the new created user model.
In this case your authentication data will only be kept on one place which is the new table .
please check the following docs link for more info regarding custom authentication models and backends

Django UniqueConstraint doesn't works with ManyToManyField, got exception FieldDoesNotExist

The migration stopped working when switching from ForeignKey between Shop and user to a ManyToManyField. I wanted shops to be able to be owned by different users at the same time:
class Shop(models.Model):
name = models.CharField('name', max_length=120)
#user = models.ForeignKey(User, on_delete=models.CASCADE) ##before
shopuser= models.ManyToManyField(User, related_name="shopusers", blank=True) ##after
class Meta:
constraints = [models.UniqueConstraint(fields=['shopuser', 'name'], name='user cant have the same shop twice!')]
## after:
#property
def get_shopuser(self):
return ", ".join([u.username for u in self.shopuser.all()])
class Warehouse(models.Model):
address = models.CharField('address', max_length=120)
user = models.ManyToManyField(User, related_name="User", blank=True)
django.core.exceptions.FieldDoesNotExist: NewShop has no field named 'shopusers'
I thought by choosing a related name I can use multiple relations to the User model? I already tried completely deleting my database (and migrations folder) and migrate from start, but tht did not help :(
Example where I would call the code:
admin.py:
#admin.register(Shop)
class ShopAdmin(admin.ModelAdmin):
list_display = ("name", "related_shopuser")
list_filter = ("name", "shopuser")
fieldsets = [("Requrired Information", {"description": "These fields are required",
"fields": (("name", "shopuser"))}),]
def related_shopuser(self, obj):
return obj.get_shopuser
Where does Djanog migrations get the NewShop from FieldDoesNotExist("%s has no field named '%s'" % (self.object_name, field_name))? Is it automatically generated from the ModelName Shop?
Issue
The UniqueConstraint on ManyToManyField will not work as expected. If you want to enforce the constraint, you should define a intermediate model and connect them using the through--(Django doc) parameter.
For the sake of simplicity, I assume you have a model as below,
class Shop(models.Model):
name = models.CharField('name', max_length=120)
user = models.ForeignKey(User, on_delete=models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(
fields=[
'user',
'name'
],
name='user cant have the same shop twice!'
)
]
and now you want make the user field to a ManyToManyField from ForeignKey
Note: I have changed the field name to users from user, which is more approriate.
Method-1 : Remove unique constraint
class Shop(models.Model):
name = models.CharField('name', max_length=120)
users = models.ManyToManyField(User)
Now, run makemigrations and migrate commands
Method-2: use an intermediate model
class ShopUserIntermediateModel(models.Model):
shop = models.ForeignKey('Shop', models.CASCADE)
user = models.ForeignKey(User, models.CASCADE)
class Meta:
constraints = [
models.UniqueConstraint(
fields=[
'shop',
'user'
],
name='user cant have the same shop twice!'
)
]
class Shop(models.Model):
name = models.CharField('name', max_length=120)
users = models.ManyToManyField(User, through=ShopUserIntermediateModel)
If not required (the project is already in production), try deleting your migration files, or at least the migration where the user field was added in the first time

Add to auth_user [duplicate]

This question already has answers here:
Extending the User model with custom fields in Django
(16 answers)
Closed 2 years ago.
I have this program that gets most of its data from the auth_user table one the django database. I don’t know how to add a new column to it and make it actually work since it doesn’t recognize it when I do. Does anybody know how you would add a column that can be used in auth_user table in the default django database.
I think this is a part that well documented on Django's documentation site. I guess you are trying to create custom user model. But I recommend you to extend user model. Extending default user model will provide you more flexibility. I hope these two links will enough. If not, please comment me about missing parts to cover.
You can override default user model or extend user model with OneToOneField relation to satisfy this requirement.
1. I'll show you how to do custom User Model.
from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin
from core.util.validators import UnicodeUsernameValidator
from .manager import UserManager
class User(AbstractBaseUser, PermissionsMixin):
"""
Username, Email and password are required. Other fields are optional.
"""
username_validator = UnicodeUsernameValidator()
username = models.CharField(
_('username'),
max_length=150,
unique=True,
help_text=_(
'150 characters or fewer. Letters, digits and #/./+/-/_ only.'),
validators=[username_validator],
error_messages={
'unique': _("A user with that username already exists."),
},
null=True,
default=None
)
email = models.EmailField(
_('email address'),
blank=False,
unique=True,
error_messages={
'unique': _("A user with that email address already exists."),
},
)
is_staff = models.BooleanField(
_('staff status'),
default=False,
help_text=_(
'Designates whether the user can log into this admin site.'),
)
is_active = models.BooleanField(
_('active'),
default=True,
help_text=_(
'Designates whether this user should be treated as active. '
'Unselect this instead of deleting accounts.'
),
)
date_joined = models.DateTimeField(_('date joined'), default=timezone.now)
objects = UserManager()
EMAIL_FIELD = 'email'
USERNAME_FIELD = 'username'
REQUIRED_FIELDS = ['email']
class Meta:
verbose_name = _('user')
verbose_name_plural = _('users')
permissions = (
('user_permission', "User Permission"),
('admin_permission', "Admin Permission"),
)
This is like a copy of what they have in their User model. here, You can add new fields.
Make sure to register this in settings.py
AUTH_USER_MODEL = 'userapp.User'
you can access extra_fields like
user.extra_field
2. I'll show you how to extend using OneToOneField.
from django.contrib.auth import get_user_model
class Profile(models.Model):
user = models.OneToOneField(get_user_model(), on_delete=models.CASCADE)
...
# Extra fields Here
then you can access the extra fields as
user.profile.extra_field

Creating models.UniqueConstraint in django, but the constraint is not applying

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.