I want to restrict other user to change object instance other then owner user.
For that I am using has_change_permission admin model function but It is not working.
My model:
class Book(models.Model):
author = models.ForeignKey(User, related_name = 'book_author')
...
In my admin.py
class BookAdmin(admin.ModelAdmin):
def has_change_permission(self, request, obj):
if request.user.is_super_user():
return True
elif request.user == obj.author:
return True
else:
return False
if obj is None:
return False
In my views.py
class BookUpdate(generic.UpdateView):
model = Book
form_class = BookUpdateForm
template_name = 'accounts/book_update.html'
def get_object(self, *args, **kwargs):
return Book.objects.get(id=self.kwargs.get('id'))
In my urls:
url(r'^update_book/(?P<id>[\w-]+)/$', views.BookUpdate.as_view(),name='update_book')
Now when any one goes to this url pattern can edit book, but I need that only author can edit this book.
Is has_change_permission right way to do so, or any other better way ?
This will only work in the Django admin interface. It looks like you're trying to update your Book instance in a custom form. You can for example overwrite the save() method of your model and check for permission here. See this part of the doc.
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.
I'm building my first real django application which has different user roles and the normal "User signal that creates an UserProfile" approach is falling short. Could you guys help me out ?
To give out more context, here are the requirements from a functional perspective :
New users are added only from the Admin and will be done by non-tech savy people, so I need a user creation flow thats intuitive and easy.
Each user has a role (Manager, Employee, Salesman, etc) with different needs and fields.
The user list needs to show both User Information and role / profile information (login, email, name, and the extra information on profile)
Initial approach :
So armed with this, I went with the recommended approach of creating a UserProfile 1-to-1 object linked to user, provide the UserProfile with a choice field where the role is set (useful for knowing what i'm dealing with when calling get_profile() ) and subclassed UserProfile into ManagerUserProfile, EmployeeUserProfile, etc.
Problem :
That works for my needs in the frontend (outside of the admin), but setting the signal to create a UserProfile when a User is created is pointless since I don't know what kind of UserProfile should I create based only on user information.
What I'm aiming at is an atomic way of creating a particular User and it's corresponding EmployeeUserProfile/ManagerUserProfile at the same time, and having a neat admin representation.
Some of my ideas:
Hide the UserAdmin and User Profile admins, create AdminModels for EmployeeUserProfile/Manager/etc and inline the User model. That way the person creating the users will see only a "New Manager" link with its corresponding fields. But they may create the UserProfile without a user ? How can i make this atomic ? How do i prevent from deleting the user within or make sure they provide all required info before allowing the profile to be saved ? -> Problem with this approach : I cannot seem to inline the user because it has no PK to UserProfile (it's the other way around).
Again, hide UserAdmin, expose the subclassed profiles Admins, and reverse the signal. When a profile is created, create a corresponding user. But for this I need to be able to provide user fields (username, pass, email, etc) from profiles admin form.
Suggestions ?
Its my first app and maybe there's a neat way for this, but I haven't found it yet.
Thanks for taking the time to read this.
Cheers,
Zeta.
I would suggest that you create a custom form, custom admin view and use that for creating users in one request, with the exact logic you need. You can get an idea of how it's done by looking at django's own custom user creation process here.
I finally found a way to implement what I needed. It may not be the cleanest, and it's open to suggestions, but it may help someone with a similar problem.
Since I needed to create only from the admin, I focused on that and build the following.
forms.py
class RequiredInlineFormSet(BaseInlineFormSet):
"""
Generates an inline formset that is required
"""
def _construct_form(self, i, **kwargs):
"""
Override the method to change the form attribute empty_permitted
"""
form = super(RequiredInlineFormSet, self)._construct_form(i, **kwargs)
form.empty_permitted = False
self.can_delete = False
return form
models.py (i'm not using signals for automatically creating a profile on user creation)
class UserProfile(models.Model):
# This field is required.
user = models.OneToOneField(User)
# Other fields here
[.......]
USER_TYPES = (
('manager', 'Manager'),
('employee', 'Employee'),
)
user_type = models.CharField(blank=True, max_length=10, choices=USER_TYPES)
def __unicode__(self):
return self.user.username
class EmployeeProfile(UserProfile):
[...]
def __init__(self, *args, **kwargs):
super(EmployeeProfile, self).__init__(*args, **kwargs)
self.user_type = 'employee'
class ManagerProfile(UserProfile):
[...]
def __init__(self, *args, **kwargs):
super(ManagerProfile, self).__init__(*args, **kwargs)
self.user_type = 'manager'
class Manager(User):
class Meta:
proxy = True
#app_label = 'auth'
verbose_name = 'manager'
verbose_name_plural = 'managers'
def save(self, *args, **kwargs):
self.is_staff = True
super(Manager, self).save(*args, **kwargs) # Call the "real" save() method.
g = Group.objects.get(name='Managers')
g.user_set.add(self)
class Employee(User):
class Meta:
proxy = True
#app_label = 'auth'
verbose_name = 'employee'
verbose_name_plural = 'employees'
def save(self, *args, **kwargs):
self.is_staff = False
super(Employee, self).save(*args, **kwargs) # Call the "real" save() method.
g = Group.objects.get(name='Employees')
g.user_set.add(self)
admin.py
class ManagerProfileAdmin(admin.StackedInline):
model = ManagerProfile
max_num = 1
extra = 1
formset = RequiredInlineFormSet
class EmployeeProfileAdmin(admin.StackedInline):
model = EmployeeProfile
max_num = 1
extra = 1
formset = RequiredInlineFormSet
class ManagerAdmin(UserAdmin):
"""
Options for the admin interface
"""
inlines = [ManagerProfileAdmin]
def queryset(self, request):
qs = super(UserAdmin, self).queryset(request)
qs = qs.filter(Q(userprofile__user_type='manager'))
return qs
class EmployeeAdmin(UserAdmin):
"""
Options for the admin interface
"""
inlines = [EmployeeProfileAdmin]
def queryset(self, request):
qs = super(UserAdmin, self).queryset(request)
qs = qs.filter(Q(userprofile__user_type='employee'))
return qs
admin.site.unregister(User)
admin.site.register(Manager, ManagerAdmin)
admin.site.register(Employee, EmployeeAdmin)
Say I'm writing a multi-blog application and I want each author to use unique titles for their articles (but unique per user, not globally unique):
class Article(models.Model):
author = models.ForeignKey('auth.User')
title = models.CharField(max_length=255)
#[...]
class Meta:
unique_together = (('title', 'owner'),)
Now, I want the author field to be auto-filled by the application:
class ArticleAdmin(ModelAdmin):
exclude = ('owner',)
def save_model(self, request, obj, form, change):
if not change:
obj.owner = request.user
obj.save()
Actually this does not work: if I try to create a new Article with an existing author-title combination, Django will not check the uniqueness (because author is excluded from the form) and I'll get an IntegrityError when it hits the database.
I thought of adding a clean method to the Article class:
def clean(self):
if Article.objects.filter(title=self.title, owner=self.owner).exists():
raise ValidationError(u"...")
But it seems that Article.clean() is called before ArticleAdmin.save_model(), so this does not work.
Several variants of this question have been asked already here, but none of the solutions seem to work for me:
I cannot use Form.clean() or other form methods that don't have the request available, since I need the request.user.
For the same reason, model-level validation is not possible.
Some answers refer to class-based views or custom views, but I'd like to remain in the context of Django's Admin.
Any ideas how I can do this without rewriting half of the admin app?
You are finding a way to bring request to customized form, in ModelAdmin, actually:
from django.core.exceptions import ValidationError
def make_add_form(request, base_form):
class ArticleForm(base_form):
def clean(self):
if Article.objects.filter(title=self.cleaned_data['title'], owner=request.user).exists():
raise ValidationError(u"...")
return self.cleaned_data
def save(self, commit=False):
self.instance.owner = request.user
return super(ArticleForm, self).save(commit=commit)
return ArticleForm
class ArticleAdmin(admin.ModelAdmin):
exclude = ('owner',)
def get_form(self, request, obj=None, **kwargs):
if obj is None: # add
kwargs['form'] = make_add_form(request, self.form)
return super(ArticleAdmin, self).get_form(request, obj, **kwargs)
What I would like to do is to display a single form that lets the user:
Enter a document title (from Document model)
Select one of their user_defined_code choices from a drop down list (populated by the UserDefinedCode model)
Type in a unique_code (stored in the Code model)
I'm not sure how to go about displaying the fields for the foreign key relationships in a form. I know in a view you can use document.code_set (for example) to access the related objects for the current document object, but I'm not sure how to apply this to a ModelForm.
My model:
class UserDefinedCode(models.Model):
name = models.CharField(max_length=8)
owner = models.ForeignKey(User)
class Code(models.Model):
user_defined_code = models.ForeignKey(UserDefinedCode)
unique_code = models.CharField(max_length=15)
class Document(models.Model):
title = models.CharField(blank=True, null=True, max_length=200)
code = models.ForeignKey(Code)
active = models.BooleanField(default=True)
My ModelForm
class DocumentForm(ModelForm):
class Meta:
model = Document
In regards to displaying a foreign key field in a form you can use the forms.ModelChoiceField and pass it a queryset.
so, forms.py:
class DocumentForm(forms.ModelForm):
class Meta:
model = Document
def __init__(self, *args, **kwargs):
user = kwargs.pop('user','')
super(DocumentForm, self).__init__(*args, **kwargs)
self.fields['user_defined_code']=forms.ModelChoiceField(queryset=UserDefinedCode.objects.filter(owner=user))
views.py:
def someview(request):
if request.method=='post':
form=DocumentForm(request.POST, user=request.user)
if form.is_valid():
selected_user_defined_code = form.cleaned_data.get('user_defined_code')
#do stuff here
else:
form=DocumentForm(user=request.user)
context = { 'form':form, }
return render_to_response('sometemplate.html', context,
context_instance=RequestContext(request))
from your question:
I know in a view you can use
document.code_set (for example) to
access the related objects for the
current document object, but I'm not
sure how to apply this to a ModelForm.
Actually, your Document objects wouldn't have a .code_set since the FK relationship is defined in your documents model. It is defining a many to one relationship to Code, which means there can be many Document objects per Code object, not the other way around. Your Code objects would have a .document_set. What you can do from the document object is access which Code it is related to using document.code.
edit: I think this will do what you are looking for. (untested)
forms.py:
class DocumentForm(forms.ModelForm):
class Meta:
model = Document
exclude = ('code',)
def __init__(self, *args, **kwargs):
user = kwargs.pop('user','')
super(DocumentForm, self).__init__(*args, **kwargs)
self.fields['user_defined_code']=forms.ModelChoiceField(queryset=UserDefinedCode.objects.filter(owner=user))
self.fields['unique_code']=forms.CharField(max_length=15)
views.py:
def someview(request):
if request.method=='post':
form=DocumentForm(request.POST, user=request.user)
if form.is_valid():
uniquecode = form.cleaned_data.get('unique_code')
user_defined_code = form.cleaned_data.get('user_defined_code')
doc_code = Code(user_defined_code=user_defined_code, code=uniquecode)
doc_code.save()
doc = form.save(commit=False)
doc.code = doc_code
doc.save()
return HttpResponse('success')
else:
form=DocumentForm(user=request.user)
context = { 'form':form, }
return render_to_response('sometemplate.html', context,
context_instance=RequestContext(request))
actually you probably want to use get_or_create when creating your Code object instead of this.
doc_code = Code(user_defined_code=user_defined_code, code=uniquecode)
I'm using Django profiles and was inspired by James Bennett to create a dynamic form (http://www.b-list.org/weblog/2008/nov/09/dynamic-forms/ )
What I need is a company field that only shows up on my user profile form when the user_type is 'pro'.
Basically my model and form look like:
class UserProfile(models.Model):
user_type = models.CharField(...
company_name = models.CharField(...
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
exclude = ('company_name',)
And I add the company_name field in init like James Bennett showed:
def __init__(self, *args, **kwargs):
super(UserProfileForm, self).__init__(*args,**kwargs)
if (self.instance.pk is None) or (self.instance.user_type == 'pro'):
self.fields['company_name'] = forms.CharField(...
The problem is that, when I try to save() an instance of UserProfileForm, the field 'company_name' is not saved...
I have gone around this by calling the field explicitly in the save() method:
def save(self, commit=True):
upf = super(UserProfileForm, self).save(commit=False)
if 'company_name' in self.fields:
upf.company_name = self.cleaned_data['company_name']
if commit:
upf.save()
return upf
But I am not happy with this solution (what if there was more fields ? what with Django's beauty ? etc.). It kept me up at night trying to make the modelform aware of the new company_name field at init .
And that's the story of how I ended up on stackoverflow posting this...
I would remove this logic from form and move it to factory. If your logic is in factory, you can have two forms:
UserProfileForm
ProUserProfileForm
ProUserProfileForm inherits from UserProfileForm and changes only "exclude" constant.
You will have then following factory:
def user_profile_form_factory(*args, instance=None, **kwargs):
if (self.instance.pk is None) or (self.instance.user_type == 'pro'):
cls = ProUserProfileForm
else:
cls = UserProfileForm
return cls(*args, instance, **kwargs)
It seems I found a solution:
def AccountFormCreator(p_fields):
class AccountForm(forms.ModelForm):
class Meta:
model = User
fields = p_fields
widgets = {
'photo': ImageWidget()
}
return AccountForm
#...
AccountForm = AccountFormCreator( ('email', 'first_name', 'last_name', 'photo', 'region') )
if request.POST.get('acforms', False):
acform = AccountForm(request.POST, request.FILES, instance=request.u)
if acform.is_valid():
u = acform.save()
u.save()
ac_saved = True
else:
acform = AccountForm(instance = request.u)
When are you expecting the user_type property to be set? This seems like something that should be handled by javascript rather than trying to do funny things with the model form.
If you want the company_name field to appear on the client after they've designated themselves as a pro, then you can 'unhide' the field using javascript.
If instead, they've already been designated a pro user, then use another form that includes the company_name field. You can sub-class the original model form in the following manner.
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
exclude = ('company_name',)
class UserProfileProForm(UserProfileForm):
class Meta:
exclude = None # or maybe tuple() you should test it
Then in your view, you can decide which form to render:
def display_profile_view(request):
if user.get_profile().user_type == 'Pro':
display_form = UserProfileProForm()
else:
display_form = UserProfileForm()
return render_to_response('profile.html', {'form':display_form}, request_context=...)
This would be the preferred way to do it in my opinion. It doesn't rely on anything fancy. There is very little code duplication. It is clear, and expected.
Edit: (The below proposed solution does NOT work)
You could try changing the exclude of the meta class, and hope that it uses the instances version of exclude when trying to determine whether to include the field or not. Given an instance of a form:
def __init__(self, *args, **kwargs):
if self.instance.user_type == 'pro':
self._meta.exclude = None
Not sure if that will work or not. I believe that the _meta field is what is used after instantiation, but I haven't verified this. If it doesn't work, try reversing the situation.
def __init__(self, *args, **kwargs):
if self.instance.user_type != 'pro':
self._meta.exclude = ('company_name',)
And remove the exclude fields altogether in the model form declaration. The reason I mention this alternative, is because it looks like the meta class (python sense of Meta Class) will exclude the field even before the __init__ function is called. But if you declare the field to be excluded afterwards, it will exist but not be rendered.. maybe. I'm not 100% with my python Meta Class knowledge. Best of luck.
What about removing exclude = ('company_name',) from Meta class? I'd think that it is the reason why save() doesn't save company_name field