Let's say I have a simple model:
class Contact(models.Model):
owner = models.ForeignKey(User, editable=False)
first_name = models.CharField(max_length=255,)
last_name = models.CharField(max_length=255,)
email = models.EmailField()
I would like to set owner (request.user, logged in user) for the object automatically when it is created. I've searched a lot of different options but most of them are related to how you do it in admin side and other ones just don't work for me. I tried this for example http://blog.jvc26.org/2011/07/09/django-automatically-populate-request-user and then I've tried many ways to override save method or some kind of pre_save signal stuff. Nothing seems to do the trick, I just get an error
IntegrityError at /new
null value in column "owner_id" violates not-null constraint
What is the right way to do that? I know that this is simple think to do but I just can't find the way to do it.
...EDIT...
My create view looks like this:
class CreateContactView(LoginRequiredMixin, ContactOwnerMixin, CreateWithInlinesView):
model = models.Contact
template_name = 'contacts/edit_contact.html'
form_class = forms.ContactForm
inlines = [forms.ContactAddressFormSet]
def form_valid(self, form):
obj = form.save(commit=False)
obj.owner = self.request.user
obj.save()
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
return reverse('contacts-list')
def get_context_data(self, **kwargs):
context = super(CreateContactView, self).get_context_data(**kwargs)
context['action'] = reverse('contacts-new')
return context
That is just one way I tried to solve that problem so far. I found that solution from http://blog.jvc26.org/2011/07/09/django-automatically-populate-request-user
Assuming you are using ContactForm ModelForm:
def contact(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
contact = form.save(commit=False)
contact.owner = request.user
contact.save()
return HttpResponseRedirect('/thanks/')
else:
# do stuff
please post the exact code of what you tried.
If your view requires that a user is logged in, make sure it is enforced. This can be done by using the #login_required decorator
If you are in a view, and using a ModelForm to create the Contact, pass the commit=False kwarg to save method (like the example in the link you posted). This will keep the contact from being created until you assign the owner = request.user.
Since a logged in user is only available within the context of a request, just make sure that you are setting owner attribute it the views when creating a new Contact
The problem is that the default implementation of the form_valid method sets self.object, which is then used by get_success_url to determine where to redirect to.
If you replace your local obj variable with self.object, you should be fine:
def form_valid(self, form):
self.object = form.save(commit=False)
self.object.owner = self.request.user
self.object.save()
return HttpResponseRedirect(self.get_success_url())
I find a quick check of the original implementation for side-effects on the Classy Class-Based Views Web site, or the Django source-code on GitHub useful for spotting any side effects I need to reproduce in a subclass implementation.
Related
I'm using Django-Rest-Framework(ViewSet approach) on my project interacting with a React app. So, I'm not using Django admin nor Django forms.
My project's structure is:
View
Serializer
Model
What I need to do is to perform actions before models method calls:
Insert the request.user on a Model field.
Start a printer process after a Model.save()
.....
I have read a lot about django-way to do on Django.docs and there, the things seems to be showed for a Django-Admin like project, which is not my case.
By other hand, by reading the Stack's answers about in other topics, the way to do seems to be like: "It will work, but, It's not the right way to do that".
According to Django's documentation, the best way to perform that supposed to be by using a new file, called admin.py, where I would to register actions binding to a Model which could support save, delete, etc., but, it's not clear if this approach is to do that or only for provide a Django-Admin way to perform an action.
# app/models.py
from django.db import models
from django.contrib.auth.models import User
class Post(models.Model):
user = models.ForeignKey(User)
content = models.TextField()
class Comment(models.Model):
post = models.ForeignKey(Post)
user = models.ForeignKey(User)
content = models.TextField()
# app/admin.py
from app.models import Post, Comment
from django.contrib import admin
class CommentInline(admin.TabularInline):
model = Comment
fields = ('content',)
class PostAdmin(admin.ModelAdmin):
fields= ('content',)
inlines = [CommentInline]
def save_model(self, request, obj, form, change):
obj.user = request.user
obj.save()
def save_formset(self, request, form, formset, change):
if formset.model == Comment:
instances = formset.save(commit=False)
for instance in instances:
instance.user = request.user
instance.save()
else:
formset.save()
admin.site.register(Post, PostAdmin)
According to the answers I have heard, the best way would use something like that on Models:
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
return super(MyModelForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
kwargs['commit']=False
obj = super(MyModelForm, self).save(*args, **kwargs)
if self.request:
obj.user = self.request.user
obj.save()
return obj
What I want to know is:
What's the best way to to perform that actions, on which files, what's the best structure.
to insert a request.user on a Model field you can use the perform_create() method on your view class. for more information visit https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/#associating-snippets-with-users which is exactly what u want!
I'm not sure what you mean by start a printer process, but you usually can override save() method on your model class for doing a process after saving a model instace.
https://www.django-rest-framework.org/api-guide/serializers/#overriding-save-directly
The best way I found to insert the request.user on the model, as a "created_by" field, was by inserting a hidden field on the model serializer with a default data, just like these:
my_field = serializers.HiddenField(default=serializers.CurrentUserDefault())
The CurrentUserDefault() is a function wich returns the user request onto serializer.
https://www.django-rest-framework.org/api-guide/validators/#advanced-field-defaults
For actions performing after/before a save/delete, what I chose to use Django Signals,wich works as a dispatcher of actions, a little like react's redux.
https://docs.djangoproject.com/en/2.2/topics/signals/
Thank you everybody for the helpful answers.
This is a bump post. I have tried various ways and went through all examples here:
https://stackoverflow.com/a/4672123/6274043
https://stackoverflow.com/a/5529770/6274043
How do you specify a default for a Django ForeignKey Model or AdminModel field?
and all doesn't seem to work for me.
# models.py in main/ app
from django.contrib.auth.models import User
class Mileage(models.Model):
owner = models.ForeignKey(User)
date = models.DateField()
... #other parameters
#admin.py
class MileageAdmin(admin.ModelAdmin):
list_display = ['date', ...]
def save_model(self, request, instance, form, change):
user = request.user
instance = form.save(commit=False)
instance.owner= user
instance.save()
form.save_m2m()
return instance
#views.py
def home(request):
form = MileageForm(request.POST or None)
context = {"form": form}
if form.is_valid():
instance = form.save()
form = MileageForm()
context = {"form": form}
return render(request, 'record_trip.html', context)
I am trying to set the default owner as the logged in user. I tried doing it in ModelAdmin like how other posts do but doesn't seem to work. Can any kind soul point out where am I doing it wrong?
It keeps throwing IntegrityError owner_id cannot be null.
Please pardon me if I made elementary mistakes above. I started coding half a year ago for leisure.
Not sure if I understood the question correctly. You are trying to set the logged-in user as the default owner of a newly created model?
Currently, you are only setting created_by and modified_by to default to the logged-in user.
#admin.py
class MileageAdmin(admin.ModelAdmin):
...
def save_model(self, request, instance, form, change):
user = request.user
instance = form.save(commit=False)
if not change or not instance.created_by:
instance.created_by = user
instance.modified_by = user
instance.save()
form.save_m2m()
return instance
But those two fields do not even exist in you example model
class Mileage(models.Model):
owner = models.ForeignKey(User)
date = models.DateField()
... #other parameters
However, you are never actually setting instance.owner to be the request.user. So instance.owner remains to be None, that's why Django complains.
You need to set it before save()ing your instance.
class MileageAdmin(admin.ModelAdmin):
...
def save_model(self, request, instance, form, change):
instance.owner = request.user
if not change or not instance.created_by:
instance.created_by = request.user
instance.modified_by = request.user
instance.save()
form.save_m2m()
return instance
It sounds like the model's save method is getting called somewhere before you've had a chance to set it to the request.user.
You could either find out where that's happening (and perhaps other answers might have a better idea of where that's happening?) or if you wanted a shortcut, you could set the owner attribute to not be a required field, trusting that you'll set it later.
owner = models.ForeignKey(User, blank=True, null=True)
That way, creating the model without the owner wouldn't throw up an integrity error, but you could trust that the owner attribute would eventually be set.
I'm trying to achieve something that I tought would be pretty basic, but can't seem to find the solution for it.
I'm creating a pretty generic view for creating and updating users in a django app. I have a 'provider' model with associated permissions. I would like to add rights management in a very checkboxy simple way. When I show the form the checkbox should be check if user have permissions to add / delete / modify, and on the other hand, when the checkbox is checked, the permissions should be set in the database.
It goes like this :
class UserUpdate(UpdateView):
form_class = UserForm
model = User
def get_initial(self):
user = self.get_object()
if user is not None and user.has_perm('core.add_provider'):
return { 'right_provider' : True }
def form_valid(self, form):
user = form.save(commit=False)
if form.right_provider:
user.user_permissions.add('core.add_provider', 'core.change_provider', 'core.delete_provider')
else:
user.user_permissions.remove('core.add_provider', 'core.change_provider', 'core.delete_provider')
return super().form_valid(form)
Then a form :
class UserForm(ModelForm):
right_provider = BooleanField(label='Right Provider', required=False)
class Meta:
model = User
fields = ['username', 'email', 'first_name', 'last_name']
Apparently it's not the way to do it, since 'UserForm' object has no attribute 'right_provider'
Am i doing it right, and if so, what is the issue with this code?
Is there litterature on how to pass data back and forth between the ModelForm and the Model?
You can get the value from the form's cleaned_data.
def form_valid(self, form):
if form.cleaned_data['right_provider']:
...
else:
...
return super().form_valid(form)
def form_valid(self, form):
if form.cleaned_data['right_provider']:
...
else:
...
return super().form_valid(form)
Note I have removed the save() call - the form will be saved automatically when you call super().
If you do need to call save in your view, I would avoid calling super(). I think it's clearer, and it avoids a potential problem of save_m2m not being called. See the docs for the save method form more information.
def form_valid(self, form):
user = form.save(commit=False)
...
user.save() # save the user to the db
form.save_m2m() # required if the form has m2m fields
return HttpResponseRedirect(self.get_success_url())
Another option is to call super() first without returning, then access self.object, and finally return the response.
def form_valid(self, form):
response = super().form_valid(form)
user = self.object
if form.cleaned_data['right_provider']:
...
else:
...
return response
Model forms expect the form fields to match the model fields. So you could add it as a field to your model.
If you only want the field on the form, you want to add it in the innit method of the form.
class UserFrom(ModelForm)
def __init__(self, **kwargs)
super(UserForm, self).__init__(**kwargs)
# probably some code here to work out the initial state of the checkbox,
self.fields['right_provider'] = forms.BooleanField(label='Right Provider', required=False)
I'm making an election information app, and I want to allow the currently logged-in user to be able to declare himself and only himself as a candidate in an election.
I'm using Django's built-in ModelForm and CreateView. My problem is that the Run for Office form (in other words, the 'create candidate' form) allows the user to select any user in the database to make a candidate.
I want the user field in the Run for Office to be automatically set to the currently logged-in user, and for this value to be hidden, so the logged-in user cannot change the value of the field to someone else.
views.py
class CandidateCreateView(CreateView):
model = Candidate
form_class = CandidateForm
template_name = 'candidate_create.html'
def form_valid(self, form):
f = form.save(commit=False)
f.save()
return super(CandidateCreateView, self).form_valid(form)
forms.py
class CandidateForm(forms.ModelForm):
class Meta:
model = Candidate
models.py
class Candidate(models.Model):
user = models.ForeignKey(UserProfile)
office = models.ForeignKey(Office)
election = models.ForeignKey(Election)
description = models.TextField()
def __unicode__(self):
return unicode(self.user)
def get_absolute_url(self):
return reverse('candidate_detail', kwargs={'pk': str(self.id)})
Remove user field from rendered form (using exclude or fields, https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#selecting-the-fields-to-use )
class CandidateForm(forms.ModelForm):
class Meta:
model = Candidate
exclude = ["user"]
Find user profile and set user field in the create view.
class CandidateCreateView(CreateView):
...
def form_valid(self, form):
candidate = form.save(commit=False)
candidate.user = UserProfile.objects.get(user=self.request.user) # use your own profile here
candidate.save()
return HttpResponseRedirect(self.get_success_url())
Assumptions
We don't want to set null=True becaues we don't want to allow null users at the model and/or database level
We don't want to set blank=True to mess with the readability of model because the user actually will not be blank
#nbm.ten solution is a good one. It has an advantages over other 'solutions'to this problem that utilized model to set the user (like this one) in nbm.ten's doesn't undermine the assumptions above. We don't want to mess with the model to fix a problem in view!
But here I add two other solutions based on django documentation (Models and request.user):
Two other solutions
1. Using the generic CreateView
from django.contrib.auth.mixins import LoginRequiredMixin
from django.views.generic.edit import CreateView
from myapp.models import Candidate
class CandidateCreate(LoginRequiredMixin, CreateView):
model = Candidate
exclude = ['user']
def form_valid(self, form):
form.instance.user = self.request.user
return super().form_valid(form)
2. Using class-based views
class CandidateForm(ModelForm):
class Meta:
model = Candidate
exclude = [ 'user',]
class CandidateAddView(LoginRequiredMixin, View):
def get(self, request, *args, **kwargs):
form = CandidateForm()
context = {'form':form}
return render(request, 'myapp/addcandidateview.html', context)
def post(self, request, *args, **kwargs):
form = CandidateForm(request.POST)
form.instance.user = request.user
if form.is_valid():
form.save()
return redirect(reverse('myapp:index'))
NOTES
Note that LoginRequiredMixin prevents users who aren’t logged in from accessing the form. If we omit that, we'll need to handle unauthorized users in form_valid() or post().
Also exclude = ['user'] prevents the user field to be shown on the form.
We used form.instance.user to set the user not form.data or form.cleaned_data they don't work
I am working on a messaging system where I want to set the originator of the message based on the currently logged in user.
class Message(models.Model):
originator = models.ForeignKey(User, related_name='+')
destination = models.ForeignKey(User, related_name='+')
subject = models.CharField(max_length=100)
content = models.TextField()
I use a ModelForm and CreateView to represent this:
class MessageForm(forms.ModelForm):
class Meta:
model = Message
fields = ('destination', 'subject', 'content')
So prior to saving this form, originator needs to be set to be the currently logged-in user. I don't think overriding the save method of the model is appropriate here, so i was going to do it in the form's save method, however I don't have access to the request variable. In another CreateView post the recommendation was to override the get_form_kwargs method:
class MyFormView(FormView):
def get_form_kwargs(self):
# pass "user" keyword argument with the current user to your form
kwargs = super(MyFormView, self).get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
However that doesn't work, since you can't actually pass the kwarg user to the ModelForm, since ModelForm doesn't accept custom arguments.
What is the best (cleanest and most practical) way to update the originator variable with the user information?
In a discussion with yuji-tomita-tomita he suggested the following, which I wanted to share:
The first method relies on his original code, sending a user kwarg to the form with get_form_kwargs, you must then modify your form model similar to this:
class MyModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.user = self.kwargs.pop('user', None)
super(MyModelForm, self).__init__(*args, **kwargs)
This way the original __init__ function gets the arguments it is expecting.
But the preferred method he suggested was:
class MyView(CreateView):
def form_valid(self, form):
obj = form.save(commit=False)
obj.user = self.request.user
obj.save()
return super(MyView, self).form_valid(form)
This works really well. When the form.save(commit=False) method executes it populates self.instance in the form object with the same instance of the model it is returning to our variable obj. When you update obj by executing obj.user = self.request.user and save it, the form object has a reference to this exact same object, and therefore the form object here already is complete. When you pass it to the original form_valid method of CreateView, it has all of the data and will be successfully inserted into the database.
If anyone has a different way of doing this I'd love to hear about it!