Here is my scenario. I want one of my model fields to be auto-fill based on whether the user is authenticated or not. Like when the user submits the form, I need to check if the user is authenticated and then fill the created_by field up with <User Object> otherwise, leave it Null if possible.
Here is my model:
class Snippet(models.Model):
# ---
create_by = models.ForeignKey(
User,
on_delete = models.CASCADE,
null=True,
)
# ---
Here is my view:
class SnippetCreateView(CreateView):
# ---
def save(self, request, *args, **kwargs):
if request.user.is_authenticated:
user = User.objects.get(username=request.user.username)
request.POST['create_by'] = user # --->> TraceBack: due to immutability..
return super().post(request, *args, **kwargs)
# ---
Since the request.POST QueryDict is immutable, how can I implement it?? I have tried multiple ways like creating a copy of that but nothing happens and it doesn't change anything.
But..
I can implement it like this and it works with no problem. I think this solution is dirty enough to find a better way. What do you think about this solution??
class Snippet.CreateView(CreateView):
# ---
def save(self, request, *args, **kwargs):
if request.user.is_authenticated:
user = User.objects.get(username=request.user.username)
data = {
'title': request.POST['title'],
# ---
'create_by': user if user else None,
}
snippet = Snippet.objects.create(**data).save()
# redirecting to Snippet.get_absolute_url()
# ---
This solution is applied only if you are using django ModelForm.
class CreateArticle(CreateView):
model = Snippet
.........
def form_valid(self, form):
user = self.request.user
form.instance.created_by = user if user else None
return super(CreateArticle, self).form_valid(form)
......
Related
Hi in my program I keep receiving the above exception and am unsure why. The issue happens when my requestLessons_view method tries to save the form.
Views.py
def requestLessons_view(request):
if request.method == 'POST':
form = RequestLessonsForm(request.POST)
if form.is_valid() & request.user.is_authenticated:
user = request.user
form.save(user)
return redirect('login')
else:
form = RequestLessonsForm()
return render(request, 'RequestLessonsPage.html', {'form': form})
forms.py
class RequestLessonsForm(forms.ModelForm):
class Meta:
model = Request
fields = ['availability', 'num_of_lessons', 'interval_between_lessons', 'duration_of_lesson','further_information']
widgets = {'further_information' : forms.Textarea()}
def save(self, user):
super().save(commit=False)
request = Request.objects.create(
student = user,
availability=self.cleaned_data.get('availability'),
num_of_lessons=self.cleaned_data.get('num_of_lessons'),
interval_between_lessons=self.cleaned_data.get('interval_between_lessons'),
duration_of_lesson=self.cleaned_data.get('duration_of_lesson'),
further_information=self.cleaned_data.get('further_information'),
)
return request
The error I receive is:
IntegrityError at /request_lessons/
NOT NULL constraint failed: lessons_request.student_id
Your .save() method is defined on the Meta class, not the form, hence the error. I would advise to let the model form handle the logic: a ModelForm can be used both to create and update the items, so by doing the save logic yourself, you basically make the form less effective. You can rewrite this to:
class RequestLessonsForm(forms.ModelForm):
class Meta:
model = Request
fields = [
'availability',
'num_of_lessons',
'interval_between_lessons',
'duration_of_lesson',
'further_information',
]
widgets = {'further_information': forms.Textarea}
def save(self, user, *args, **kwargs):
self.instance.student = user
return super().save(*args, **kwargs)
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
I know this has been discussed and it's basic but I can't find what is wrong with it. I've pulled up my old projects (which works!) and corresponded to what I did. It never reaches to update in serializer, and I'm at lost why.
I dont know what else I'm missing.
Error:
{"last_name":["This field may not be null."],"pass…
null."],"email":["This field may not be null."]}, status: 400
frontend patch('api/getprofile')
django/DRF serializer:
class UserSerializer(serializers.ModelSerializer):
first_name = serializers.CharField()
last_name = serializers.CharField()
email = serializers.EmailField()
password = serializers.CharField(style={'input_type': 'password'})
class Meta:
model = User
fields = '__all__'
def create(self, validated_data):
user = User.objects.create(
username=validated_data.get('username'),
email=validated_data.get('email'),
password=validated_data.get('password')
)
user.set_password(validated_data.get('password'))
user.save()
return user
def update(self, instance, validated_data):
#print instance <-- if never gets here... is update not update
for key, value in validated_data.items():
if value:
print value
setattr(instance, key, value)
instance.save()
return instance
views.py
class UserRetrieveUpdateAPIView(generics.RetrieveUpdateAPIView):
serializer_class = UserSerializer
permission_classes = (IsAuthenticated, )
queryset = User.objects.all()
def get_object(self):
return self.request.user
def update(self, request, *args, **kwargs):
serializer = UserSerializer(data=request.data, partial=True)
serializer.is_valid(raise_exception=True)
self.perform_update(serializer)
instance = serializer.instance
return Response(UserSerializer(instance=instance).data, status=status.HTTP_200_OK)
The only implementation that you may need to provide for your APIView is the get_object method.
From the source for mixins.UpdateMixins, update (for HTTP PUT requests) and partial_update are implemented as you have it.
The override for the mixins.UpdateMixins.update you provide allows partial updates on HTTP PUT requests and misses passing the model instance to the serializer for the update. i.e.
serializer = UserSerializer(self.get_object(), data=request.data, partial=True)
I however suggest to not perform the override for mixins.UpdateMixins.update in the current manner.
Use the standard handling of the HTTP requests implemented in mixins.UpdateMixins and only provide your implementation for .get_object().
You do this already with UserRetrieveUpdateAPIView.get_object().
I want to connect each post with the logged in user who posted it.
models.py
from django.conf import settings
from django.db import models
# Create your models here.
class Campagin(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1)
title = models.CharField(max_length=120)
media = models.FileField()
description = models.TextField(max_length=220)
timestamp = models.DateTimeField(auto_now=False, auto_now_add=True)
updated = models.DateTimeField(auto_now=True, auto_now_add=False)
def __str__(self):
return self.title`
As you can see the posts were made by two different users, but the relation shows that it is made by the first user
this image shows the registered users..
Views.py
class NewCampagin(LoginRequiredMixin, CreateView):
template_name = 'campagin/new_campagin.html'
model = Campagin
fields = ['title','media','description']
def get_absolute_url(self):
return reverse('campagin:active_campagin')
Okay so CreateView allows you to specify the model and fields attributes to implicitly create a form for you. It's quite neat for quick form submissions but in your case, you will need to make some customizations before saving the Campaign object into the database (linking up the current logged in user).
As a result, you will need to create your own form first (create a file called forms.py which can be next to your views.py) and enter this code:
class CampaignForm(ModelForm): # Import ModelForm too.
def __init__(self, *args, **kwargs):
# We need to get access the currently logged in user so set it as an instance variable of CampaignForm.
self.user = kwargs.pop('user', None)
super(CampaignForm, self).__init__(*args, **kwargs)
class Meta:
model = models.Campaign # you need to import this from your models.py class
fields = ['title','media','description']
def save(self, commit=True):
# This is where we need to insert the currently logged in user into the Campaign instance.
instance = super(CampaignForm, self).save(commit=False)
# Once the all the other attributes are inserted, we just need to insert the current logged in user
# into the instance.
instance.user = self.user
if commit:
instance.save()
return instance
Now that we have our forms.py all ready to go we just need to modify your views.py:
class NewCampagin(LoginRequiredMixin, CreateView):
template_name = 'campagin/new_campagin.html'
form_class = forms.CampaignForm # Again, you'll need to import this carefully from our newly created forms.py
model = models.Campaign # Import this.
queryset = models.Campaign.objects.all()
def get_absolute_url(self):
return reverse('campagin:active_campagin') # Sending user object to the form, to verify which fields to display/remove (depending on group)
def get_form_kwargs(self):
# In order for us to access the current user in CampaignForm, we need to actually pass it accross.
# As such, we do this as shown below.
kwargs = super(NewCampaign, self).get_form_kwargs()
kwargs.update({'user': self.request.user})
return kwargs
What's actually happening with my POST requests under the bonnet??
Note: This is just extra information for the sake of learning. You do
not need to read this part if you don't care about how your class
based view is actually handling your post request.
Essentially CreateView looks like this:
class CreateView(SingleObjectTemplateResponseMixin, BaseCreateView):
"""
View for creating a new object instance,
with a response rendered by template.
"""
template_name_suffix = '_form'
Doesn't look that interesting but if we analyse BaseCreateView:
class BaseCreateView(ModelFormMixin, ProcessFormView):
"""
Base view for creating an new object instance.
Using this base class requires subclassing to provide a response mixin.
"""
def post(self, request, *args, **kwargs):
self.object = None
return super(BaseCreateView, self).post(request, *args, **kwargs)
we can see we are inheriting from two very important classes ModelFormMixin and ProcessFormView. Now the line, return super(BaseCreateView, self).post(request, *args, **kwargs), essentially calls the post function in ProcessFormView which looks like this:
def post(self, request, *args, **kwargs):
"""
Handles POST requests, instantiating a form instance with the passed
POST variables and then checked for validity.
"""
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
As you can see, your CreateView really just boils down to this small post function which simply gets a specified form and validates + saves it. There's 2 questions to ask at this point.
1) What does form = self.get_form() do since I didn't even specify my form?
2) What is self.form_valid(form) actually doing?
To answer the first question, self.get_form() essentially calls another function form_class = self.get_form_class() and this function is actually found in ModelFormMixin (the one where inherited from!):
def get_form_class(self):
"""
Returns the form class to use in this view.
"""
if self.fields is not None and self.form_class:
raise ImproperlyConfigured(
"Specifying both 'fields' and 'form_class' is not permitted."
)
if self.form_class:
return self.form_class
else:
if self.model is not None:
# If a model has been explicitly provided, use it
model = self.model
elif hasattr(self, 'object') and self.object is not None:
# If this view is operating on a single object, use
# the class of that object
model = self.object.__class__
else:
# Try to get a queryset and extract the model class
# from that
model = self.get_queryset().model
if self.fields is None:
raise ImproperlyConfigured(
"Using ModelFormMixin (base class of %s) without "
"the 'fields' attribute is prohibited." % self.__class__.__name__
)
# THIS IS WHERE YOUR FORM WAS BEING IMPLICITLY CREATED.
return model_forms.modelform_factory(model, fields=self.fields)
As you can see, this function is where your form was being implicitly created (see very last line). We needed to add more functionality in your case so we created our own forms.py and specified form_class in the views.py as a result.
To answer the second question, we need to look at the function (self.form_valid(form)) call's source code:
def form_valid(self, form):
"""
If the form is valid, save the associated model.
"""
# THIS IS A CRUCIAL LINE.
# This is where your actual Campaign object is created. We OVERRIDE the save() function call in our forms.py so that you could link up your logged in user to the campaign object before saving.
self.object = form.save()
return super(ModelFormMixin, self).form_valid(form)
So here we are simply saving the object.
I hope this helps you!
More information at https://docs.djangoproject.com/en/1.10/ref/class-based-views/generic-editing/#createview
I am using Django models to create the fields for a form. I would like to have the user's username automatically detected in and be filled out, this way I can hide it in my form (instead of having them choose their username from a long list that has everyones username). To do this I am using:
current_user = request.user
and then setting the default to current_user. However, I keep getting this error:
NameError: name 'request' is not defined
I'm assuming you can't use requests in Django models, but is there anyway to get around this? Here is the relevant sections of my models.py file:
class StockTickerSymbol(models.Model):
StockName = models.CharField(max_length=7, unique=True)
current_user = request.user
user = models.ForeignKey(User, default=current_user)
Anyone know how I can use requests in my models, or somehow call the variable current_user?
Here you haven't imported request in that model class scope. This is how you can get user:
# model
class StockTickerSymbol(models.Model):
StockName = models.CharField(max_length=7, unique=True)
user = models.ForeignKey(User)
def save(self,**kwargs):
if kwargs.has_key('request') and self.user is None:
request = kwargs.pop('request')
self.user= request.user
super(StockTickerSymbol, self).save(**kwargs)
#views:
def post(self, request):
if form.is_valid():
sts=StockTickerSymbol()
sts.StockName= form.cleaned_data['StockName']
if form.cleaned_data['user'] is None: #null check
sts.save(request=request)
else:
sts.user= form.cleaned_data['user']
sts.save(request=request)
For modelform:
class SomeForm(forms.ModelForm):
...
def save(self, commit=True ,*args, **kwargs):
request = None
if kwargs.has_key('request'):
request = kwargs.pop('request')
m = super(SomeForm, self).save(commit=False, *args, **kwargs)
if m.user is None and request is not None:
m.user= request.user
m.save()
in views:
def post(self, request):
if form.is_valid():
form.save(request=request)
return ...
When creating an object initially I use the currently logged-in user to assign the model field 'owner'.
The model:
class Account(models.Model):
id = models.AutoField(primary_key=True)
owner = models.ForeignKey(User)
name = models.CharField(max_length=32, unique=True)
description = models.CharField(max_length=250, blank=True)
Serializer to set owner:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = models.Account
fields = ('name', 'description')
def restore_object(self, attrs, instance=None):
instance = super().restore_object(attrs, instance)
request = self.context.get('request', None)
setattr(instance, 'owner', request.user)
return instance
It is possible for a different user in my system to update another's Account object, but the ownership should remain with the original user. Obviously the above breaks this as the ownership would get overwritten upon update with the currently logged in user.
So I've updated it like this:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = models.Account
fields = ('name', 'description')
def restore_object(self, attrs, instance=None):
new_instance = False
if not instance:
new_instance = True
instance = super().restore_object(attrs, instance)
# Only set the owner if this is a new instance
if new_instance:
request = self.context.get('request', None)
setattr(instance, 'owner', request.user)
return instance
Is this the recommended way to do something like this? I can't see any other way, but I have very limited experience so far.
Thanks
From reviewing #zaphod100.10's answer. Alternatively, in the view code (with custom restore_object method in above serializer removed):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.DATA, files=request.FILES)
if serializer.is_valid():
serializer.object.owner = request.user
self.pre_save(serializer.object)
self.object = serializer.save(force_insert=True)
self.post_save(self.object, created=True)
headers = self.get_success_headers(serializer.data)
return Response(serializer.data, status=status.HTTP_201_CREATED,
headers=headers)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Basically you want the owner to be set on creation and not on subsequent updates. For this I think you should set the owner in the POST view. I think it is more logical and robust that way. Update is done via PUT view so your data should always be correct since no way on updation the owner can be changed if the owner is not editable on PUT.
For making the views you can use DRF's generic class based views. Use the RetrieveUpdateDeleteView as it is. For ListCreateView override the post method. Use a django model form for validating the data and creating an account instance.
You will have to copy the request.DATA dict and insert 'owner' as the current user.
The code for the POST method can be:
def post(self, request, *args, **kwargs):
data = deepcopy(request.DATA)
data['owner'] = request.user
form = AccountForm(data=data)
if form.is_valid():
instance = form.save(commit=false)
instance.save()
return Response(dict(id=instance.pk), status=status.HTTP_201_CREATED)
return Response(form.errors, status=status.HTTP_400_BAD_REQUEST)
Potential other option using pre_save which I think seems to be intended for just this kind of thing.
class AccountList(generics.ListCreateAPIView):
serializer_class = serializers.AccountSerializer
permission_classes = (permissions.IsAuthenticated)
def get_queryset(self):
"""
This view should return a list of all the accounts
for the currently authenticated user.
"""
user = self.request.user
return models.Account.objects.filter(owner=user)
def pre_save(self, obj):
"""
Set the owner of the object to the currently logged in user as this
field is not populated by the serializer as the user can not set it
"""
# Throw a 404 error if there is no authenticated user to use although
# in my case this is assured more properly by the permission_class
# specified above, but this could be any criteria.
if not self.request.user.is_authenticated():
raise Http404()
# In the case of ListCreateAPIView this is not necessary, but
# if doing this on RetrieveUpdateDestroyAPIView then this may
# be an update, but if it doesn't exist will be a create. In the
# case of the update, we don't wish to overwrite the owner.
# obj.owner will not exist so the way to test if the owner is
# already assigned for a ForeignKey relation is to check for
# the owner_id attribute
if not obj.owner_id:
setattr(obj, 'owner', self.request.user)
I think this is the purpose of pre_save and it is quite concise.
Responsibilities should be split here, as the serializer/view only receives/clean the data and make sure all the needed data is provided, then it should be the model responsibility to set the owner field accordingly. It's important to separate these two goals as the model might be updated from elsewhere (like from an admin form).
views.py
class AccountCreateView(generics.CreateAPIView):
serializer_class = serializers.AccountSerializer
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, *args, **kwargs):
# only need this
request.data['owner'] = request.user.id
return super(AccountCreateView, self).post(request, *args, **kwargs)
models.py
class Account(models.Model):
# The id field is provided by django models.
# id = models.AutoField(primary_key=True)
# you may want to name the reverse relation with 'related_name' param.
owner = models.ForeignKey(User, related_name='accounts')
name = models.CharField(max_length=32, unique=True)
description = models.CharField(max_length=250, blank=True)
def save(self, *args, **kwargs):
if not self.id:
# only triggers on creation
super(Account, self).save(*args, **kwargs)
# when updating, remove the "owner" field from the list
super(Account, self).save(update_fields=['name', 'description'], *args, **kwargs)