Make models belong to users in django - python

I am using django.contrib.auth and I am wondering how I can make an instance of a model "belong to" a particular user. Do I need to define a foreign key in the model that points to the user? I want users to keep their CRUD to themselves.
# The objects created from this model should belong
# to the user who created them and should not be
# viewable by other users of the website.
from django.contrib.auth.models import User
class Classroom(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=30)
def __unicode__ (self):
return self.name
Thanks.

This is the general approach I take:
Creating models that belong to the user
On the model, have a foreign key to the user.
Define a custom ModelForm that requires a user to be passed to the constructor (__init__()). Also, override the form's save() method so that the user passed to the constructor is added to the model instance, before it's saved.
Define class based views based on CreateView etc. that override the get_form_kwargs method to pass the form the user stored in the request.
Here's the code:
# models.py
from django.db import models
from django.contrib.auth.models import User
class MyModel(models.Model):
"A model that belongs to a user."
user = models.ForeignKey(User)
# forms.py
from django import forms
from .models import MyModel
class MyModelForm(forms.ModelForm):
"""A form for creating/updating MyModels that accepts
the user as a kwarg.
"""
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(MyModelForm, self).__init__(*args, **kwargs)
def save(self, *args, **kwargs):
self.instance.user = self.user
return super(MyModelForm, self).save(*args, **kwargs)
class Meta:
model = MyModel
# Don't show the user on the form
exclude = ('user',)
# views.py
from django.views.generic import CreateView
from .models import MyModel
from .forms import MyModelForm
class MyModelCreateView(CreateView):
"View for creating a MyModel belonging to the current user."
model = MyModel
form_class = MyModelForm
def get_form_kwargs(self, *args, **kwargs):
form_kwargs = super(MyModelCreateView,
self).get_form_kwargs(*args, **kwargs)
# Pass the current user to the form constructor
form_kwargs['user'] = self.request.user
return form_kwargs
Listing models that belong to the user
This is more straightforward - you extend ListView and filter the queryset by the current user. (Read more about ListView.)
Editing models that belong to the user
Use a similar approach to MyModelCreateView, but extending an UpdateView instead. The important thing here is to check that the user has permission to edit:
# views.py
from django.views.generic import UpdateView
from django.core.exceptions import PermissionDenied
...
class MyModelUpdateView(UpdateView):
"View for the current user editing a MyModel."""
model = MyModel
form_class = MyModelForm
def get_object(self, *args, **kwargs):
object = super(MyModelUpdateView, self).get_object(*args, **kwargs)
# Raise a permission denied if the current user doesn't
# 'own' the MyModel they're trying to edit.
if object.user != self.request.user:
raise PermissionDenied
def get_form_kwargs(self, *args, **kwargs):
form_kwargs = super(MyModelCreateView,
self).get_form_kwargs(*args, **kwargs)
# Pass the current user to the form constructor
form_kwargs['user'] = self.request.user
return form_kwargs

If you would like to have multiple objects belong to the user, a foreign key field to the User object will work. Note, you can still use a foreign key, and have one instance by passing
the unique attribute to the field definition.
If one object (and only one) and belong to a user, use a one to one field. A one to one field can be accessed from either side of the models
You can use user.classroom or classroom.user, these bindings can be changed with the related_name attribute of one to one field definitions.

Related

Automatically updating a model field when it is created

I would like to automatically update a model field when it is created. So this is my situation. I have a custom User model that my customer can use to login. After they login, they will go to the account/profile page, which has a link to a form. Currently, when the user submits the form, it creates an instance of the LevelTest model(which is something I just need for the website to work). Here is the view class for the form:
class LevelTestView(generic.CreateView):
template_name = "leads/leveltest.html"
form_class = LevelTestModelForm
def get_success_url(self):
return reverse("profile-page")
and here is the LevelTestModelForm:
class LevelTestModelForm(forms.ModelForm):
class Meta:
model = LevelTest
fields = (
'first_name',
'last_name',
'age',
'username',
)
What I want to fill in automatically is the username field. In fact, I wish it doesn't even show up on the form itself when the user types in. The username is a field in the User Model, so I just want the new LevelTest's username field filled in with the current user's username. Hence, I used a post_save signal like below(which doesn't work):
def post_leveltest_created_signal(sender, instance, created, **kwargs):
if created:
instance.objects.update(
username=instance.user.username,
description='Add Description',
phone_number=instance.user.cellphone,
email=instance.user.username,
)
post_save.connect(post_leveltest_created_signal, sender=LevelTest)
I hope you guys could help me tweek the post_save signal, so that when the user creates a LevelTest instance, the LevelTest's username field(as well as the phone_number and email) is filled in with the user model's information. Thanks a lot!
If I understand you correct, you don't need to use signals, you can save username easier:
Extend get_form_kwargs method in your CreateView, like that:
class LevelTestView:(generic.CreateView)
...
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['user'] = self.request.user
return kwargs
Extend __init__ and save method in your Form, like that:
class LevelTestModelForm(forms.ModelForm):
...
def __init__(self, user, *args, **kwargs):
self.user = user
super().__init__(*args, **kwargs)
...
def save(self, commit=True):
leveltest = super().save(commit=False)
# I think it would be better if you saved only 'user' instance
# like this - leveltest.user = self.user (of course if you have fk to user model)
leveltest.username = self.user.username
leveltest.phone_number=self.user.cellphone
leveltest.email=self.user.username
leveltest.save()
return leveltest
I think #KIN1991's answer is pretty awesome, but you can minimize/optimize the code even more by just overriding the form_valid method. Like this:
class LevelTestView:(generic.CreateView)
...
def form_valid(self, form, *args, **kwargs):
user = self.request.user
form.instance.username = user.username
form.instance.phone_number=user.cellphone,
form.instance.email=user.username
return super().form_valid(form, *args, **kwargs)

Specify Django proxy model in admin foreign key

In Django 1.11, I have a model Friend, and a proxy model Relative:
class FriendManager(models.Manager):
def get_queryset(self):
return super(RelativeManager, self).get_queryset().filter(is_relative=False)
class Friend(models.Model):
# Model fields defined here
objects = FriendManager()
class RelativeManager(models.Manager):
def get_queryset(self):
return super(RelativeManager, self).get_queryset().filter(is_relative=True)
class Relative(Friend):
class Meta:
proxy = True
objects = RelativeManager()
def save(self, *args, **kwargs):
self.is_relative = True
super(Relative, self).save(*args, **kwargs)
I also have a model FriendPortrait, which has a foreign key field friend:
class FriendPortrait(models.Model):
friend = models.ForeignKey(Friend)
And a proxy on that:
class RelativePortrait(FriendPortrait):
class Meta:
proxy = True
Now, I want the detail view for RelativePortraits to only show relatives in the drop-down for friend.
admin.py:
#admin.register(RelativePortrait)
class RelativePortraitAdmin(admin.ModelAdmin):
fields = ('friend')
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'friend':
kwargs['queryset'] = Relative.objects.all()
return super(RelativePortraitAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
This works, in that only relatives are displayed in the friend drop-down. However, when I try to save a portrait, Django admin gives me a validation error:
friend instance with id 14 does not exist.
How can I specify that I want to use a proxy model for my foreign key in the RelativePortraitAdmin?
The problem here is that your ForeignKey points to the Friend model. The model's default manager filters out all relatives, so this will not work.
A simple way to solve this would be to restructure your models a bit. Introducing something like a generic Person model and having Friend and Relative inherit from it with proxy=True. The Person model shouldn't have a manager that pre-filters the instances; then you could have your ForeignKey point to person.

Why signature of the form "save()" method should match base Form class in django?

For instance, I have following code:
from django.db import models
from django.forms import ModelForm
from django.contrib.auth.models import User
from django.views.generic import FormView
class MyModel(models.Model)
owner = models.ForeignKey(User)
description = models.TextField()
class MyForm(ModelForm):
class Meta:
model = MyModel
def save(self, owner, commit=True):
self.instance.owner = owner
return super().save(commit)
class MyView(FormView):
success_url = '/'
form_class = MyForm
template_name = 'my_template.html'
def form_valid(self, form):
form.save(self.request.user)
return super().form_valid(form)
If I run pylint on that code, it gives following error:
[W0221(arguments-differ), MyForm.save] Arguments number differs from overridden 'save' method
Is it a bad practice to do that? Should I set owner in the view form_valid method?
Take a look at the docs: https://docs.djangoproject.com/en/dev/topics/forms/modelforms/#the-save-method
Instead of using a different signature, why don't you call save with commit=False; this will return the model instance. You can then add the appropriate attributes, in this case owner, and save the model directly.

Add a field to the ModelAdmin Form

I want to give users the possibility to create multiple events at once. Therefore I would like to add a field to the admin-add-page where a number of repetitions can be specified. Then I want to override the save function and create multiple events (based on the input). I started writing some code but the admin add page does not update at all. I will show you the code below:
In admins.py:
class EventAdmin(admin.ModelAdmin):
form = EventForm
admin.site.register(Event, EventAdmin)
In forms.py
from django import forms
from django.db import models
from calendar_app.models import Event
class EventForm(forms.ModelForm):
name = models.CharField(max_length=100) # just for testing purpose
class Meta:
model = Event
def __init__(self, *args, **kwargs):
super(EventForm, self).__init__(*args, **kwargs)
if not kwargs.has_key('instance'):
self.fields['name'] = forms.CharField(label='Name')
self.base_fields['name'] = forms.CharField(label='Name')
def save(self, commit=True):
model = super(EventForm, self).save(commit=False)
# Save all the fields...
if commit:
model.save()
return model
But the "name" field is not showing up when I add an event. Any ideas? Thanks!
I used models.CharField instead of forms.CharField. See comments.

Override save_model on Django InlineModelAdmin

I have a model that has a user field that needs to be auto-populated from the currently logged in user. I can get it working as specified here if the user field is in a standard ModalAdmin, but if the model I'm working with is in an InlineModelAdmin and being saved from the record of another model inside the Admin, it won't take.
Here's what I think is the best solution. Took me a while to find it... this answer gave me the clues: https://stackoverflow.com/a/24462173/2453104
On your admin.py:
class YourInline(admin.TabularInline):
model = YourInlineModel
formset = YourInlineFormset
def get_formset(self, request, obj=None, **kwargs):
formset = super(YourInline, self).get_formset(request, obj, **kwargs)
formset.request = request
return formset
On your forms.py:
class YourInlineFormset(forms.models.BaseInlineFormSet):
def save_new(self, form, commit=True):
obj = super(YourInlineFormset, self).save_new(form, commit=False)
# here you can add anything you need from the request
obj.user = self.request.user
if commit:
obj.save()
return obj
I know I'm late to the party, but here's my situation and what I came up with, which might be useful to someone else in the future.
I have 4 inline models that need the currently logged in user.
2 as a created_by type field. (set once on creation)
and the 2 others as a closed_by type field. (only set on condition)
I used the answer provided by rafadev and made it into a simple mixin which enables me to specify the user field name elsewhere.
The generic formset in forms.py
from django.forms.models import BaseInlineFormSet
class SetCurrentUserFormset(forms.models.BaseInlineFormSet):
"""
This assume you're setting the 'request' and 'user_field' properties
before using this formset.
"""
def save_new(self, form, commit=True):
"""
This is called when a new instance is being created.
"""
obj = super(SetCurrentUserFormset, self).save_new(form, commit=False)
setattr(obj, self.user_field, self.request.user)
if commit:
obj.save()
return obj
def save_existing(self, form, instance, commit=True):
"""
This is called when updating an instance.
"""
obj = super(SetCurrentUserFormset, self).save_existing(form, instance, commit=False)
setattr(obj, self.user_field, self.request.user)
if commit:
obj.save()
return obj
Mixin class in your admin.py
class SetCurrentUserFormsetMixin(object):
"""
Use a generic formset which populates the 'user_field' model field
with the currently logged in user.
"""
formset = SetCurrentUserFormset
user_field = "user" # default user field name, override this to fit your model
def get_formset(self, request, obj=None, **kwargs):
formset = super(SetCurrentUserFormsetMixin, self).get_formset(request, obj, **kwargs)
formset.request = request
formset.user_field = self.user_field
return formset
How to use it
class YourModelInline(SetCurrentUserFormsetMixin, admin.TabularInline):
model = YourModel
fields = ['description', 'closing_user', 'closing_date']
readonly_fields = ('closing_user', 'closing_date')
user_field = 'closing_user' # overriding only if necessary
Be careful...
...as this mixin code will set the currently logged in user everytime for every user. If you need the field to be populated only on creation or on specific update, you need to deal with this in your model save method. Here are some examples:
class UserOnlyOnCreationExampleModel(models.Model):
# your fields
created_by = # user field...
comment = ...
def save(self, *args, **kwargs):
if not self.id:
# on creation, let the user field populate
self.date = datetime.today().date()
super(UserOnlyOnCreationExampleModel, self).save(*args, **kwargs)
else:
# on update, remove the user field from the list
super(UserOnlyOnCreationExampleModel, self).save(update_fields=['comment',], *args, **kwargs)
Or if you only need the user if a particular field is set (like boolean field closed) :
def save(self, *args, **kwargs):
if self.closed and self.closing_date is None:
self.closing_date = datetime.today().date()
# let the closing_user field set
elif not self.closed :
self.closing_date = None
self.closing_user = None # unset it otherwise
super(YourOtherModel, self).save(*args, **kwargs) # Call the "real" save() method.
This code could probably be made way more generic as I'm fairly new to python but that's what will be in my project for now.
Only the save_model for the model you're editing is executed, instead you will need to use the post_save signal to update inlined data.
(Not really a duplicate, but essentially the same question is being answered in Do inline model forms emmit post_save signals? (django))
I had a similar issue with a user field I was trying to populate in an inline model. In my case, the parent model also had the user field defined so I overrode save on the child model as follows:
class inline_model:
parent = models.ForeignKey(parent_model)
modified_by = models.ForeignKey(User,editable=False)
def save(self,*args,**kwargs):
self.modified_by = self.parent.modified_by
super(inline_model,self).save(*args,**kwargs)
The user field was originally auto-populated on the parent model by overriding save_model in the ModelAdmin for the parent model and assigning
obj.modified_by = request.user
Keep in mind that if you also have a stand-alone admin page for the child model you will need some other mechanism to keep the parent and child modified_by fields in sync (e.g. you could override save_model on the child ModelAdmin and update/save the modified_by field on the parent before calling save on the child).
I haven't found a good way to handle this if the user is not in the parent model. I don't know how to retrieve request.user using signals (e.g. post_save), but maybe someone else can give more detail on this.
Does the other model save the user? In that case you could use the post_save signal to add that information to the set of the inlined model.
Have you tried implementing custom validation in the admin as it is described in the documentation? Overriding the clean_user() function on the model form might do the trick for you.
Another, more involved option comes to mind. You could override the admin template that renders the change form. Overriding the change form would allow you to build a custom template tag that passes the logged in user to a ModelForm. You could then write a custom init function on the model form that sets the User automatically. This answer provides a good example on how to do that, as does the link on b-list you reference in the question.

Categories