Django redirect_url with parameter - python

I have a simple UpdateView which updates instances of my Profile model.
class ProfileUpdateView(UpdateView):
model = Profile
fields = ['first_name', 'last_name']
It's working fine both GET and POST, but since the GET uses an url parameter, I want also the redirect_url to redirect to the updated profile.
My current urls.py:
from profiles.views import ProfileUpdateView
urlpatterns = [
path('self/<slug:slug>', ProfileUpdateView.as_view(success_url='/'), name='self_profile')
]
It is currently redirecting to the main url, but I don't figure out how to make the redirect_url parameter redirect to the profile modified on the handled POST.
Let's say I do http://localhost:8000/profiles/self/demo, (GET), then I modify my first_name and submit the request. I want the redirection to go to /profiles/self/demo again.
I know I could override def post() on the view and achieve it, but since I only want to deal with the redirection I wanted to know if exists any other elegant solution.
EDIT:
Additional info: the slug url parameter will always be request.user in this case (since I use other view (a DetailView) to see other users' profiles).
Thanks!!

Override the get_success_url(...) method as
class ProfileUpdateView(UpdateView):
model = Profile
fields = ['first_name', 'last_name']
def get_success_url(self):
return '/profiles/self/demo'
Hence, you don't need to specify the success_url attribute in the urls.py
from profiles.views import ProfileUpdateView
urlpatterns = [
path('self/<slug:slug>', ProfileUpdateView.as_view(), name='self_profile')
]

Related

How to redirect user to the desired page after login using LoginRequiredMixin in django?

I have created a mini project called ToDoList App. I have used class based views for create, update and delete functions. There is MyTasks icon on navbar.
What I want?
If the user is not logged in and he clicks on MyTasks icon, he should be redirected to the login page and after login to MyTasks page.
I have used LOGIN_REDIRECT_URL = 'home' and LOGIN_URL='login' in settings.py file
If I write LOGIN_REDIRECT_URL = 'task_list', it is working fine else not.
Below is the code from views.py file:
class TaskListView(LoginRequiredMixin, ListView):
"""List of all tasks."""
model = MyTasks
context_object_name = 'tasks'
template_name = 'task_list.html'
This is from urls.py file:
path('tasks/', views.TaskListView.as_view(), name="task_list"),
I have used the attribute redirect_field_name='next' and tried to override dispatch method in the above class, still it is not working.
The output I am getting is, after login, it is getting redirected to homepage instead of my tasks page. Any help is much appreciated.
Below is the screenshot from the terminal:
[1]: https://i.stack.imgur.com/KtsIG.png
You should use absolute path instead of reverse url.
if you want to use reverse urls you need to use reverse function.
for example:
from django.urls import reverse
LOGIN_REDIRECT_URL = reverse("app_name:url_name")
but this is a global setting and will work for all of your views.
if you want to do it just for one of your views you can do it:
class YourView(LoginRequiredMixin, View):
login_url = '/login/' # defaults to settings.LOGIN_URL
redirect_field_name = 'redirect_to' # querystring name => ? redirect_to=current_url

Accessing a hyperlinkedRelatedField object from a permission class

I am trying to make an api backend of something like reddit. I want to ensure that whoever is creating a post (model Post) within a particular subreddit is a member of that subreddit (subreddit model is Sub). Here is my latest effort, which works but seems pretty sloppy, and the serializer for some context.
Post permissions.py
class IsMemberOfSubOrReadOnly(BasePermission):
def has_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
return True
elif request.data:
# prevent creation unless user is member of the sub
post_sub_pk = get_pk_from_link(request.data['sub'])
user = request.user
user_sub_pks = [sub.pk for sub in user.subs.all()]
if not (post_sub_pk in user_sub_pks):
return False
return True
Post serializers.py
from .models import Post
from redditors.models import User
from subs.models import Sub
class PostSerializer(serializers.HyperlinkedModelSerializer):
poster = serializers.HyperlinkedRelatedField(
view_name='user-detail',
#queryset=User.objects.all(),
read_only=True
)
sub = serializers.HyperlinkedRelatedField(
view_name='sub-detail',
queryset=Sub.objects.all()
)
class Meta:
model = Post
fields = ('url', 'id', 'created', 'updated', 'title', 'body',
'upvotes', 'sub', 'poster')
The issue with this approach is that since 'sub' is a hyperlinkedRelatedField on the Post serializer what I get back from request.data['sub'] is just the string hyperlink url. I then have a function, get_pk_from_link that uses regex to read the pk off the end of the url. Then I can use that to grab the actual model I want and check things. It would be nice if there were a more direct way to access the Sub model that is involved in the request.
I have tried searching the fields of the arguments that are available and I can't find a way to get to the Sub object directly. Is there a way to access the Sub model object through the hyperlink url?
I have also solved this problem by just using a serializer field validator (not shown above), but I am interested to know how to do it this way too. Maybe this is just a bad idea and if so please let me know why.
You are right, parsing the url is not the way to go. Since you want to perform the permission check before creating a Post object, I suspect you cannot use object level permissions either, because DRF does not call get_object in a CreateAPIView (since the object does not exist in the database yet).
Considering this is a "business logic" check, a simpler approach would be to not have that permission class at all and perform the check in your perform_create hook in your view (I had asked a similar question about this earlier):
from rest_framework.exceptions import PermissionDenied
# assuming you have a view class like this one for creating Post objects
class PostList(generics.CreateApiView):
# ... other view stuff
def perform_create(self, serializer):
sub = serializer.get('sub') # serializer is already validated so the sub object exists
if not self.request.user.subs.filter(pk=sub.pk).exists():
raise PermissionDenied(detail='Sorry, you are not a member of this sub.')
serializer.save()
This saves you hassle of having to perform that url parsing as the serializer should give you the Sub object directly.

Django-allauth restrict registration to list of emails

I'm using Django-allauth, I have a list of emails and I want to restrict registration to this list. My idea was to check the signing up user email and, if not in the emails list, stop registration process and redirect.
As suggested by Chetan Ganji I tried editing allauth.account.views.SignupView but it does not subscribe the form_valid method. How can i do that? Thank you for help
from allauth.account.views import SignupView
class AllauthCustomSignupView(SignupView):
def form_valid(self, form):
email = form.cleaned_data['email']
auth_user_list = [ 'email_1',
'email_2',
...
]
if not any(email in s for s in auth_user_list):
return reverse('url')
return super(MySignupView, self).form_valid(form)
You can do it by extending the DefaultAccountAdapter class. You have to figure out a way to store and fetch the restricted list on demand.
You can then use the adapters and raise validation error in the registration from. Extend a DefaultAccountAdapter and override the clean_email method. Create an adapter.py in your project directory and extend the default adapter class.
from allauth.account.adapter import DefaultAccountAdapter
from django.forms import ValidationError
class RestrictEmailAdapter(DefaultAccountAdapter):
def clean_email(self,email):
RestrictedList = ['Your restricted list goes here.']
if email in RestrictedList
raise ValidationError('You are restricted from registering. Please contact admin.')
return email
Finally, point the account adapter in settings.py to your extended class.
ACCOUNT_ADAPTER = 'YourProject.adapter.RestrictEmailAdapter'
Maybe try with this code
class AllauthCustomSignupView(SignupView):
def form_valid(self, form):
email = form.cleaned_data['email']
auth_user_list = [ 'email_1',
'email_2',
...
]
if email in auth_user_list:
return reverse('blocked-email') # whatever url, make sure that the url is defined inside form_valid or in approriate location.
else:
return super(AllauthCustomSignupView, self).form_valid(form)
class BlockedEmailView(TemplateView):
template_name = "blocked-email.html"
Add below line to your urls.py
url(r'^signup/$', views.AllauthCustomSignupView.as_view(), name="signup"),
url(r'^blocked/email$', views.BlockedEmailView.as_view(), name="blocked-email"),
Also, you will need to change the action attribute of the form that SignupView has. So, your will have to override the template of that view, keep everything else the same, just change the action to point to "signup/".

Django allauth Redirect on Password Change Success

This question has been brought up before here: https://github.com/pennersr/django-allauth/issues/468
It's closed and is a few years old, which could explain why its not working for me.
I am simply trying to redirect to a different page other than the change password page after the password is successfully changed.
Here is my code, which is not making the page redirect on success.
#ursl.py
url(r'accounts/password/change', views.custom_password_change),
url(r'^accounts/', include('allauth.urls'))
...
#views.py
from allauth.account.views import PasswordChangeView
from django.contrib.auth.decorators import login_required
class CustomPasswordChangeView(PasswordChangeView):
print("Getting Here")
#property
def success_url(self):
print('Inside Success')
return '/unknown/'
custom_password_change = login_required(CustomPasswordChangeView.as_view())
After submitting a password change, my terminal is printing "Getting Here" so it is definitely getting to that custom view. But its not printing "Inside Success".
Any help is appreciated! Thanks!
success_url is a property, not a method. So you can either do:
class CustomPasswordChangeView(PasswordChangeView):
success_url = '/unknown/'
Or, if you need dynamic URLs (say, with access to User), you can override get_success_url(), a class method that basically just returns the success_url property. An example of that below.
class CustomPasswordChangeView(LoginRequiredMixin, PasswordChangeView):
"""
Overriding Allauth view so we can redirect to profile home.
"""
def get_success_url(self):
"""
Return the URL to redirect to after processing a valid form.
Using this instead of just defining the success_url attribute
because our url has a dynamic element.
"""
success_url = reverse('users:user-detail',
kwargs={'username': self.request.user.username})
return success_url

Django-Registration & Django-Profile, using your own custom form

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

Categories