How to pass data between views and forms using form_valid - methods

I have a simple UserRegistration view:
class UserRegistration(generic.CreateView):
form_class = RegisterForm
template_name = 'registration/registration.html'
success_url = reverse_lazy('home')
def form_valid(self, form):
user = form.save()
login(self.request, user, backend='django.contrib.auth.backends.ModelBackend')
return redirect(self.success_url)
Which logs the User in after they register. I wanted to send an email confirmation letting the User know they successfully signed up. So I added a method to my RegisterForm:
class RegisterForm(UserCreationForm):
email = forms.EmailField()
first_name = forms.CharField(max_length=100)
last_name = forms.CharField(max_length=100)
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password1', 'password2')
def registration_email(request):
send_mail(
'Welcome aboard!',
'Thanks for signing up...',
NOTIFICATION_EMAIL,
['example#email.com'],
fail_silently=True
)
Then I call it in my view with form.registration_email().
Now, this works but it's hardcoded. I want the recipient email to be the new User's email they just entered. That info should be available in the view, but how do I pass that to my form? I just can't figure out how to pass data from my views to my form methods, so that I can then call those methods in my view if form is valid.
Should I be doing it like this? Or should I be doing all this in form_valid instead? I also plan on using Celery in the future, so is there a preferred way with regards to that as well?

Where should to be send_email method depends on your logic in project.
In your case:
class RegisterForm(UserCreationForm):
...
def registration_email(self, request):
# it should be only after from.is_valid()
# to protect us:
if self.is_valid():
send_mail(
'Welcome aboard!',
'Thanks for signing up...',
NOTIFICATION_EMAIL,
[self.cleaned_data['email']],
fail_silently=True
)

Related

Serializing custom model fields in TokenObtainPairSerializer

Im using djangorestframework-simplejwt. I want to make TokenObtainPairView take in first_name, last_name, phone_number and email to authenticate instead of the default username and password, so that I can instead make a /register endpoint that will both register and login in a single request. (For reference this is the urlpattern typically used with TokenObtainPairView typically
path('api/token/', TokenObtainPairView.as_view(), name='token_obtain_pair'),
I've been able to make this work with writing normal serializer fields like below
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
first_name = serializers.CharField()
last_name = serializers.CharField()
email = serializers.EmailField()
class MyTokenObtainPairView(TokenObtainPairView):
serializer_class = MyTokenObtainPairSerializer
Is there a way to use ModelSerializer to do this instead? e.g something like this (the below doesn't work)
class MyTokenObtainPairSerializer(TokenObtainPairSerializer, ModelSerializer):
class Meta:
model = User
fields = ['first_name', 'last_name', 'email']

last_login does not update by API in django rest

I created a simple endpoint to create and update users. It's work fine except for the field last_login that not update when a user login by API.
My example:
My urls:
router.register(r"user", foo.UserViewSet)
My Serializer:
class UserSerializer(serializers.ModelSerializer):
password = serializers.CharField(write_only=True)
def create(self, validated_data):
user = User.objects.create(
username=validated_data['username'],
first_name=validated_data['first_name'],
last_name=validated_data['last_name'],
email=validated_data["email"],
last_login=validated_data["last_login"],
)
user.set_password(validated_data['password'])
user.save()
return user
class Meta:
model = User
exclude = (
"groups",
"user_permissions",
)
My View:
class UserViewSet(viewsets.ModelViewSet):
queryset = User.objects.all()
pagination_class = StandardPagination
serializer_class = UserSerializer
model = User
def update(self, request, *args, **kwargs):
user = self.get_object()
serializer = self.get_serializer(
user, data=request.data, partial=True
)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
return Response(serializer.data)
When I log with any user (in API) the field last_login is always null. By django admin is ok.
Update
I had some progress and problems putting the code in my UserViewSet:
def perform_authentication(self, request):
user = request.user
user.last_login = timezone.now()
user.save()
return user
Ok. last_login is registering, but only in user endpoint and every request in this endpoint.
As far as my own investigation on this issue goes, it turns out that last_login field is tightly coupled with default Django authentication system (session authentication), that is why this field is updated when you use Django Admin, as Admin uses session authentication.
In your case, it looks like some other type of authentication is used, and the signal which updates this field is not triggered. See the link below where the signal is handled:
https://github.com/django/django/blob/5f8495a40ab1554e81ac845484da890dd390e1d8/django/contrib/auth/models.py#L14
So, in order to update this field properly you should probably do it manually.
Here is one more discussion on this matter:
last_login field is not updated when authenticating using Tokenauthentication in Django Rest Framework
Maybe it could be the default setting, try this in settings.py:
SIMPLE_JWT = {
// ...
'UPDATE_LAST_LOGIN': True,
// ...
}
source: https://django-rest-framework-simplejwt.readthedocs.io/en/latest/settings.html

What's wrong with my API and how can I fix it if it's true

My viewset:
class UserViewSet(SerializerByActionMixin, LoggingMixin, viewsets.ModelViewSet):
queryset = User.objects.all().order_by('-date_joined')
serializers = {
'default': UserSerializer,
'create': UserCreateSerializer,
'update': UserUpdateSerializer,
}
def get_permissions(self):
# allow non-authenticated user to create via POST
return (AllowAny() if self.request.method == 'POST'
else IsStaffOrTargetUser()),
My serializer for update:
class UserUpdateSerializer(serializers.HyperlinkedModelSerializer):
avatar = serializers.ImageField(max_length=None, allow_empty_file=True, required=False)
class Meta:
model = User
fields = (
'first_name',
'last_name',
'email',
'mobile_phone',
'avatar',
)
def update(self, instance, validated_data):
for field, value in validated_data.items():
setattr(instance, field, value)
instance.save()
log.info("User %s, %s changed his profile" % (instance.username, instance.email))
return instance
Frontend in VueJs.
I'm not very strong at Vue, but my partner writes that I can not upload the avatar to the file system, well, as I understand the form on the frontend does not load the image and does not save ...
Tell me if I need to write a separate presentation and serializer for the avatar, or maybe I did not add some method? I'm not very experienced in DRF?
My thanks!

Django REST framework restrict posting and browsable Api-fields

I use the Django Rest framework together with an JavaScript app. I have some difficulties to get the posting of new data items right with the generic ModelViewSet
Most importantly I want to restrict what a poster can submit
(they should only be allowed to post items that have the user_id of this user (the authenticated user of the session).
I don't know when/where I should check for this? Is this a validation problem?
How I understand the permission classes is that they restrict the method (Post/Get) or check for user groups.
Also my user field in the item model is a foreign key to the user model
so the browsable api suggest in the Html-form a dropdown with the information about other users. (their email adresses and some other fields).
My data items look like this
[{
"id": 792,
"name": "test",
"category": 1,
"value": 5,
"user": "33"
}]
Here is my Serializer and the Viewset:
class ItemSerializer(serializers.ModelSerializer):
class Meta:
model = Item
fields = ('id',
'name',
'category',
'value',
'user',
)
class ItemViewSet(viewsets.ModelViewSet):
serializer_class = ItemSerializer
def get_queryset(self):
return Item.objects.filter(user=self.request.user)
I can't believe this issue with the DRF Create/Update (Post/Put) form isn't more widely discussed.
It's a huge data privacy issue - e.g. One can restrict the List API view to only show items owned by a User via overriding the get_queryset method inside as below:
# views.py
class ItemViewSet(viewsets.ModelViewSet):
def get_queryset(self):
return Item.objects.filter(user=self.request.user)
But as OP notes, when accessing the API Create/Post or Update/Put form for the ItemViewSet, there is seemingly no easy way to restrict the user options to the user itself.
I had a similar issue myself building a survey platform, where I want to restrict choice of survey/question/options etc. to those owned by the user, and prevent users from inadvertently seeing each other's data.
Jocelyn's answer works for the OP's particular situation where we already know that the Item.user must equal request.user, so we override this on the perform_create method.
But Jocelyn's solution is insufficient for situations where you do not know in advance what the relationship between model instances will be (e.g. in my case where a new question objected could be added to any one of a user's surveys).
The solution I came up with was the nuclear option: do away with the Viewset altogether for Create and Update functionality, and use a set of custom views.APIView classes instead, as below (adapted for the case of the OP, only showing Create).
class ItemCreateView(views.APIView):
def post(self, request, format=None):
post_user_id = int(request.data['user'].split('/')[-2])
request_user_id = request.user.id
serializer = ItemSerializer(data=request.data, context={'request': request})
if post_user_id == request_user_id:
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
else:
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
else:
return Response('Not Allowed: Owner is not User', status=status.HTTP_401_UNAUTHORIZED)
Please note, I'm using a HyperlinkedModelSerializer rather than a plain ModelSerializer, hence the need for .split('/')[-2] to grab the post_user_id
Handling the user field
First set the user field to be readonly:
# serializers.py
class ItemSerializer(serializers.ModelSerializer):
user = serializers.ReadOnlyField()
class Meta:
model = Item
fields = ('id',
'name',
'category',
'value',
'user',
)
Then auto-set the user id on creation:
# views.py
class ItemViewSet(viewsets.ModelViewSet):
serializer_class = ItemSerializer
def get_queryset(self):
return Item.objects.filter(user=self.request.user)
def perform_create(self, serializer):
serializer.save(user=self.request.user.customer)
Handling permissions
Just use standard permissions mechanism to define a custom one :
# permissions.py
from rest_framework import permissions
class IsOwner(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
return (request.user.is_authenticated() and
(obj.user == request.user.customer))
...and use it in your viewset :
# views.py
from permissions import IsOwner
class ItemViewSet(viewsets.ModelViewSet):
permission_classes = [IsOwner]
...

Django 1.3 CreateView, ModelForm and filtering fields by request.user

I am trying to filter a field on a ModelForm. I am subclassing the generic CreateView for my view. I found many references to my problem on the web, but the solutions do not seem to work (for me at least) with Django 1.3's class-based views.
Here are my models:
#models.py
class Subscriber(models.Model):
user = models.ForeignKey(User)
subscriber_list = models.ManyToManyField('SubscriberList')
....
class SubscriberList(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=70)
....
Here is my view:
#views.py
class SubscriberCreateView(AuthCreateView):
model = Subscriber
template_name = "forms/app.html"
form_class = SubscriberForm
success_url = "/app/subscribers/"
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.user = self.request.user
return super(SubscriberCreateView, self).form_valid(form)
Here is my original form for adding a Subscriber, with no filter:
#forms.py
class SubscriberForm(ModelForm):
class Meta:
model = Subscriber
exclude = ('user', 'facebook_id', 'twitter_id')
Here is my modified form, attempting to filter, but doesn't work:
#forms.py
class SubscriberForm(ModelForm):
class Meta:
model = Subscriber
exclude = ('user', 'facebook_id', 'twitter_id')
def __init__(self, user, **kwargs):
super(SubscriberForm, self).__init__(**kwargs)
self.fields['subscriber_list'].queryset = SubscriberList.objects.filter(user=user)
If I change this modified form as so:
def __init__(self, user=None, **kwargs)
It works - It brings me NO subscriber lists. But any way I try to pass the request user, I invariably get a a name "request" or name "self" not defined error.
So, how can I modify my code to filter subscriber_list by the request.user, and still use Django 1.3's CreateView.
I see you've been posting this question in various places.. and the way I found that is because I was trying to figure out the same thing. I think I just got it working, and here's what I did. I overwrote get_form() from FormMixin to filter a specific form fields queryset:
class MyCreateView(CreateView):
def get_form(self, form_class):
form = super(MyCreateView,self).get_form(form_class) #instantiate using parent
form.fields['my_list'].queryset = MyObject.objects.filter(user=self.request.user)
return form

Resources