I have the following subclass:
class UserProfile(User):
user = models.OneToOneField(User, parent_link=True)
And an UserProfileAdmin:
class UserProfileAdmin(admin.ModelAdmin):
# stuff
admin.site.register(UserProfile, UserProfileAdmin)
This admin displays a change password link /admin/customer/userprofile/1548/password/ under the password field. But i get the following error:
invalid literal for int() with base 10: '1548/password'
I want to use the same change password form as in auth.User and after change is made i want to be redirected to UserProfileAdmin. How can i do that?
It is the expected behavior because:
/admin/customer/userprofile/1548/password/
Wants to display the change form for userprofile with id '1548/password'.
Extending the User class is not the way to store extra data per user. Read the documentation on Storing additional information about users for instruction on how to do it the right way.
That said, you can if you want this url to open the admin change password page, you can do a redirect as such:
# put **before** include(admin.site.urls)
url(r'/admin/customer/userprofile/(?P<id>\d+)/password/$', 'views.redirect_to_password'),
And in views.py:
from django import shortcuts
def redirect_to_password(request, id):
return shortcuts.redirect('/admin/auth/user/%s/password/' % id)
If you also want to redirect the /admin/auth/user/1234 to /admin/customer/userprofile/1234, then you could add this:
url(r'/admin/auth/user/(?P<id>\d+)/$', 'views.redirect_to_customer_changeform'),
Would work with a similar view:
from django import shortcuts
def redirect_to_customer_changeform(request, id):
return shortcuts.redirect('/admin/customer/userprofile/%s/' % id)
Related
I have app where user can add url to favorite in database with ManyToMany relationship. So every user can have a lot of urls and every url can have a lot of users. I have problem with creating restriction to avoid adding the same url for user. I mean I want to create the mechanism where url can be added a lot of time for users, but only one per user.
In my models I have:
from django.db import models
from django.contrib.auth.models import User
class Repository(models.Model):
url = models.CharField(max_length=250,unique=False)
user = models.ForeignKey(User,related_name='user',on_delete=models.CASCADE,default='')
repositorys = models.ManyToManyField(User,related_name='user')
And in my views:
def favorites(request):
url = request.session.get('url')
repo = Repository(url=url,user=request.user)
repo.save()
repo.repositorys.add(request.user)
user = User.objects.get(username=request.user.username)
repos = user.users.all()
return render(request, 'favorites.html',{'url':url,'repos':repos})
Favorites function is called on clicked button in my template. When I click the above function is executed and redirect to /favorites. The problem is, when I click again on button and Im logged as the same user, this url is added again to database. Same problem with refreshing favorites.html. Is there any logic way to solve this problem?
EDIT:
view favorites:
def favorites(request):
url = request.session.get('url')
try:
repo = Repository(url=url,user=request.user)
repo.save()
repo.repositorys.add(request.user)
user = User.objects.get(username=request.user.username)
except IntegrityError as e:
return render_to_response('base.html')
repos = user.users.all()
return render(request, 'favorites.html', {'url': url, 'repos': repos})
My idea here was to return all urls for user then move adding to try block. When there is a IntegrityError then move to base.html and later will display some messages
Use unique_together in your model to manage uniqueness at the database level
class Repository(models.Model):
url = models.CharField(max_length=250,unique=False)
user = models.ForeignKey(User,related_name='user',on_delete=models.CASCADE,default='')
class Meta:
unique_together = [['url', 'user']]
then when you want to add a URL for a specific user, handle unique error to prevent a user to add a specific URL twice or more.
try:
# add a Repository
except IntegrityError:
# raise an error
I have made a custom user model using the AbstractUser , removed the username and replaced it with email and extended the model, tried creating superuser and it worked and also created some users by a signup form , logged in the admin interface and it worked however when tried to create a login form for the users it fails
I tried this but it didn't work
def LoginView(request):
if request.method == 'POST':
form = AuthenticationForm(data=request.POST)
if form.is_valid():
user = form.get_user()
login(request,user)
return redirect('accounts:login')
else:
form = AuthenticationForm()
return render(request,'accounts/login.html', {'form':form})
then i tried this
class LoginView(FormView):
form_class = AuthenticationForm
template_name = 'login.html'
def form_valid(self, form):
email = form.cleaned_data['email']
password = form.cleaned_data['password']
user = authenticate(email=email, password=password)
# Check here if the user is an admin
if user is not None and user.is_active:
login(self.request, user)
return HttpResponseRedirect(self.success_url)
else:
return self.form_invalid(form)
Obviously i expect the user to be logged in
i think the code in this post is badly formatted. mainly it's my fault as i'm new to this platform
I've developed almost the same setup that you're describing (I didn't remove the username field, just stopped using it). If you haven't already seen it, Django's documentation at https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#substituting-a-custom-user-model is quite helpful.
There are a couple of important things that need to be set up correctly for this to work.
The USERNAME_FIELD on your model should be set to the name of your email field.
The AUTH_USER_MODEL needs to point to your custom user model.
class MyUser(AbstractUser):
USERNAME_FIELD = 'email'
AUTH_USER_MODEL = 'customauth.MyUser'
Since you've removed the username field altogether you might need to subclass django.contrib.auth.forms.AuthenticationForm and django.contrib.auth.views.LoginView to avoid breaking things, but Django should handle a different authentication field quite well.
If you do wind up needing to subclass the view, https://ccbv.co.uk/projects/Django/2.2/django.contrib.auth.views/LoginView/ is a great place to look over all the methods to see what's going on.
Edit - On Subclassing and it's necessity
What I was saying about possibly needing to subclass certain things was influenced by https://docs.djangoproject.com/en/2.2/topics/auth/customizing/#writing-a-manager-for-a-custom-user-model. I wasn't sure if there were other parts of the authentication system that would need you to customize them because you removed the username field.
I've read through some of the source code for Django's authentication system. Here's the path that's being followed.
When the POST request is made to Django's authentication view the authentication form is validated. https://github.com/django/django/blob/2.2.2/django/contrib/auth/forms.py#L191
The authenticate function is called. This iterates through the backends set up and tries to authenticate on each of them. https://github.com/django/django/blob/2.2.2/django/contrib/auth/__init__.py#L62
Django's built-in authentication backend gets the user if it exists using the natural key. https://github.com/django/django/blob/2.2.2/django/contrib/auth/backends.py#L16
We can see in the base manager that the natural key used is the field named by USERNAME_FIELD. https://github.com/django/django/blob/2.2.2/django/contrib/auth/base_user.py#L43
If the form is valid, meaning that the user is authenticated properly, the user is then logged in. https://github.com/django/django/blob/2.2.2/django/contrib/auth/views.py#L88
My reaction is that it looks like Django should work out of the box for your use case. You shouldn't need to write a backend. Here's the extent of the code my gut says you should have to write.
from django.contrib.auth import views as auth_views
from django.shortcuts import resolve_url
class LoginView(auth_views.LoginView):
template_name = 'accounts/login.html'
def get_success_url(self):
return resolve_url('accounts:login')
I don't know if this could be of any use to somebody but I can confirm that Django can authenticate you well with its own Login view if you just replace the username with an email field on your custom user model (as long as you specify the USERNAME_FIELD on the custom user model and are indeed using it by declaring it in the settings).
As I was expecting this behavior I designed a custom HTML form with email/password inputs and used the same principles I would use with the original user model authentication. It was failing though and I understood it was because I wasn't adapting my form to the original Login view expectations or would have worked from the start.
Just make sure to remember that the form HTML input tag for the email address needs to have "type" set to "email" but "id" set to "id_username" and "name" to "username".
This means you can just replace username with an email field and authenticate normally. I did not even declare a Login view for my login form to work, the Django view automacally used from the core is just being called at /accounts/login and working on its own. I'm working on Django 3.2
I'm new to Django, creating a site where I want logged in users to see there own data provided in a table. This table has a field username.
I want the users to see there own data in a listview. I can't figure out how I can query, using the username from User. To give you an idea of what I am doing, this is what I have as code: (I tried multiple other ways, but I can't get a string with the User login Name.
from django.contrib.auth.models import User
from django.views.generic import ListView
username = User.username
class RoosterListView(LoginRequiredMixin, ListView):
queryset = Roosters.objects.filter(startvc__range=(DatumStart, DatumEind),username=CurrentUser).order_by("startvc")[:35]
Thanks so much in advance.
Remove the username = User.username line - User is the model class, not the current user instance.
You can access the current user if you set queryset, as this is loaded when the module is imported, not when the request is made. If you override the get_queryset method, you can access the user with self.request.user.
class RoosterListView(LoginRequiredMixin, ListView):
def get_queryset(self):
return Roosters.objects.filter(startvc__range=(DatumStart, DatumEind), username=self.request.user.username).order_by("startvc")[:35]
you can get the username of your logged in user by
username = request.user
you can simply pass your request parameter around to get all the information of the current session and do whatever query you wanna do.
I would like to implement a 2nd admin site which provides a subset of feature of the primary admin site. That's possible and described in the Django docs
However, I would like to limit access on the primary admin site. Some users can access the 2ndary site but not the primary site.
In order to implement that feature, I would like these users not to be in the staff (is_staff=False) and rewrite the AdminSite.has_permission
class SecondaryAdminSite(AdminSite):
def has_permission(self, request):
if request.user.is_anonymous:
try:
username = request.POST['username']
password = request.POST['password']
except KeyError:
return False
try:
user = User.objects.get(username = username)
if user.check_password(password):
return user.has_perm('app.change_onlythistable')
else:
return False
except User.DoesNotExist:
return False
else:
return request.user.has_perm('app.change_onlythistable')
Unfortunately, this approach doesn't work. The user can login but can't see anything in the secondary admin site.
What's wrong with this approach?
Any idea how to implement this feature?
Thanks in advance
Here's what worked for me with Django >= 3.2.
Create a subclass of AdminSite
Override the has_permission() method to remove the is_staff check.
Override the login_form to use AuthenticationForm.
AdminSite uses AdminAuthenticationForm, which extends AuthenticationForm and adds a check for is_staff.
Code
# PROJECT/APP/admin.py
from django.contrib.admin import AdminSite
from django.contrib.admin.forms import AuthenticationForm
class MyAdminSite(AdminSite):
"""
App-specific admin site implementation
"""
login_form = AuthenticationForm
site_header = 'Todomon'
def has_permission(self, request):
"""
Checks if the current user has access.
"""
return request.user.is_active
site = MyAdminSite(name='myadmin')
I think that your approach should now be possible: http://code.djangoproject.com/ticket/14434 (closed 5 weeks ago)
However, the explicit "is_staff" check is still done in two places (apart from the staff_member_required decorator):
django.contrib.admin.forms.AdminAuthenticationForm.clean()
On top of "has_permission()" you'd need to provide your non-staff AdminSite with a "login_form" that doesn't do the is_staff check, so could just subclass and adjust clean() accordingly.
templates/admin/base.html
would need to be slightly customized.
The div with id "user-tools" is only shown for active staff members. I'm assuming that's done because the login form also uses this template, and someone could be logged in as an active non-staff member but still should'nt see those links.
What's wrong with this approach? Any idea how to implement this feature?
What's wrong with this approach is that permissions and groups can already provide you with what you need. There is no need to subclass AdminSite if all you need is to divide users.
This is probably why this feature is so poorly documented, IMHO
I am making use of django-registration and django-profile to handle registration and profiles. I would like to create a profile for the user at the time of registration. I have created a custom registration form, and added that to the urls.py using the tutorial on:
http://dewful.com/?p=70
The basic idea in the tutorial is to override the default registration form to create the profile at the same time.
forms.py - In my profiles app
from django import forms
from registration.forms import RegistrationForm
from django.utils.translation import ugettext_lazy as _
from profiles.models import UserProfile
from registration.models import RegistrationProfile
attrs_dict = { 'class': 'required' }
class UserRegistrationForm(RegistrationForm):
city = forms.CharField(widget=forms.TextInput(attrs=attrs_dict))
def save(self, profile_callback=None):
new_user = RegistrationProfile.objects.create_inactive_user(username=self.cleaned_data['username'],
password=self.cleaned_data['password1'],
email=self.cleaned_data['email'])
new_profile = UserProfile(user=new_user, city=self.cleaned_data['city'])
new_profile.save()
return new_user
In urls.py
from profiles.forms import UserRegistrationForm
and
url(r'^register/$',
register,
{'backend': 'registration.backends.default.DefaultBackend', 'form_class' : UserRegistrationForm},
name='registration_register'),
The form is displayed, and i can enter in City, however it does not save or create the entry in the DB.
You're halfway there - you've successfully built a custom form that replaces the default form. But you're attempting to do your custom processing with a save() method on your model form. That was possible in older versions of django-registration, but I can see from the fact that you specified a backend in your URL conf that you're using v0.8.
The upgrade guide says:
Previously, the form used to collect
data during registration was expected
to implement a save() method which
would create the new user account.
This is no longer the case; creating
the account is handled by the backend,
and so any custom logic should be
moved into a custom backend, or by
connecting listeners to the signals
sent during the registration process.
In other words, the save() method on the form is being ignored now that you're on version 0.8. You need to do your custom processing either with a custom backend or with a signal. I chose to create a custom back-end (if anyone has gotten this working with signals, please post code - I wasn't able to get it working that way). You should be able to modify this to save to your custom profile.
Create a regbackend.py in your app.
Copy the register() method from DefaultBackend into it.
At the end of the method, do a query to get the corresponding User instance.
Save the additional form fields into that instance.
Modify the URL conf so that it points to BOTH the custom form AND the custom back-end
So the URL conf is:
url(r'^accounts/register/$',
register,
{'backend': 'accounts.regbackend.RegBackend','form_class':MM_RegistrationForm},
name='registration_register'
),
regbackend.py has the necessary imports and is basically a copy of DefaultBackend with just the register() method, and the addition of:
u = User.objects.get(username=new_user.username)
u.first_name = kwargs['first_name']
u.last_name = kwargs['last_name']
u.save()
As described in my comment on Django Trac ticket I made a metaclass and mixin to allow multiple inheritance for ModelForm Django forms. With this you can simply make a form which allows registration with fields from user and profile models at the same time without hard-coding fields or repeating yourself. By using my metaclass and mixin (and also fieldset mixin) you can do:
class UserRegistrationForm(metaforms.FieldsetFormMixin, metaforms.ParentsIncludedModelFormMixin, UserCreationForm, UserProfileChangeForm):
error_css_class = 'error'
required_css_class = 'required'
fieldset = UserCreationForm.fieldset + [(
utils_text.capfirst(UserProfileChangeForm.Meta.model._meta.verbose_name), {
'fields': UserProfileChangeForm.base_fields.keys(),
})]
def save(self, commit=True):
# We disable save method as registration backend module should take care of user and user
# profile objects creation and we do not use this form for changing data
assert False
return None
__metaclass__ = metaforms.ParentsIncludedModelFormMetaclass
Where UserCreationForm can be for example django.contrib.auth.forms.UserCreationForm form and UserProfileChangeForm a simple ModelForm for your profile model. (Do not forget to set editable to False in your foreign key to User model.)
With django-registration backend having such register method:
def register(self, request, **kwargs):
user = super(ProfileBackend, self).register(request, **kwargs)
profile, created = utils.get_profile_model().objects.get_or_create(user=user)
# lambda-object to the rescue
form = lambda: None
form.cleaned_data = kwargs
# First name, last name and e-mail address are stored in user object
forms_models.construct_instance(form, user)
user.save()
# Other fields are stored in user profile object
forms_models.construct_instance(form, profile)
profile.save()
return user
Be careful that registration signal is send at the beginning of this method (in method in superclass) and not at the end.
In the same manner you can make a change form for both user and profile information. Example for this you can find in my comment on Django Trac ticket mentioned above.
With registration 0.8 and later:
Create a subclass of registration.backends.default.views.RegistrationView in your views.py or equivalent:
from registration.backends.default.views import RegistrationView
class MyRegistrationView(RegistrationView):
form_class= MyCustomRegistrationForm
def register(self, request, **cleaned_data):
new_user= super(MyRegistrationView, self).register(request, **cleaned_data)
# here create your new UserProfile object
return new_user