Am using django-permission on simple test app (almost identical to the example used in the docs) to try to figure out how it works. I have read the documentation and tried to use the example app provided on this link.
The issue is when the author of an article is not able to edit/ delete the article.
The user in question has been granted all permissions in the admin section.
Key code listed below - any help much appreciated
test_app/models.py
class Article(models.Model):
created_by = models.ForeignKey(User)
created = models.DateField(auto_now_add=True)
modified = models.DateField(auto_now=True)
title = models.CharField(max_length=100)
content = models.TextField()
class Meta:
app_label = 'test_app'
from permission import add_permission_logic
from permission.logics import AuthorPermissionLogic
add_permission_logic(Article, AuthorPermissionLogic(
field_name='created_by',
any_permission = False,
change_permission = True,
delete_permission = True,
))
test_app/views.py
#permission_required('change_article')
def change_article(request, *args, **kwargs):
pk = kwargs.pop('pk')
template = 'test_app/edit.html'
article = models.Article.objects.get(id=pk)
if request.method == 'POST':
form = forms.Article_form(request.POST, instance=article)
if form.is_valid():
article = form.save(commit=False)
article.created_by = request.user
article.title = form.cleaned_data['title']
article.content = form.cleaned_data['content']
article.save()
return HttpResponseRedirect('/test/')
else:
raise Http404
else:
form = forms.Article_form(instance=article)
return render(request, template_name=template, context={'form':form})
test_app/perms.py
PERMISSION_LOGICS = (
('test_app.Article', AuthorPermissionLogic()),
)
EDIT
In the end there is a longer discussion on the project Github page available on this link.
While the objective of the question was resolved - it turns out that the function itself is a bit of a legacy function that is prone to unexpected behavior. The advice of the project owner is to use class based views rather than function based views.
I don't really get what
The user in question has been granted all permissions in the admin section.
means (not sure what "admin section" is) but
You don't need perms.py while you already add a permission logic in your models.py.
You need to use test_app.change_article instead (<app_label>.<perm>_<model_name>)
By the way, while you don't need perms.py so it's not a matter but the instance of AuthorPermissionLogic in perms.py is not properly configured while you haven't specified field_name there (the default value of field_name is 'author' if you don't specified.) https://github.com/lambdalisue/django-permission/blob/master/src/permission/conf.py#L24
Related
I'm fairly new to Django and am working on making user profile pages accessible by using the user's username in the url, e.g. mysite.com/profile/someusername
I'll be having links to the profile in a couple places, but the first place I'm experimenting on is in my navbar to access the logged-in user's page.
base.html
<a class="dropdown-item" href="{% url 'fillups:user_profile' username=user.username %}" class="btn btn-simple">Overview</a>
This correctly displays the currently logged-in user's name, for the case of this example we'll user the username seconduser
This is the url pattern I'm using for this:
path('profile/<str:username>/',views.UserProfile.as_view(),name='user_profile')
So far, the navbar will display the username, seconduser, and when I click the button I'm brought to the url /profile/seconduser/, which is what I want.
The problem is, I'm not able to now use the username in my view to query the objects for the given user. Here is what I have for this view so far
views.py
class UserProfile(TemplateView):
template_name = 'fillups/user_profile.html'
slug_field = "username"
slug_url_kwarg = "username"
def get_context_data(self, **kwargs):
context = super(UserProfile, self).get_context_data(**kwargs)
usr = get_object_or_404(User, username=self.kwargs.get("username"))
overview_stats = {
'total_cars': Car.objects.filter(username=usr).count(),
'total_fillups': Fillup.objects.filter(username=self.request.user).count(),
'total_distance': Fillup.objects.filter(username=self.request.user).aggregate(Sum('trip_distance')),
'total_gallons': Fillup.objects.filter(username=self.request.user).aggregate(total_gallons = Round(Sum('gallons'),4)),
'avg_price': Fillup.objects.filter(username=self.request.user).aggregate(avg_price = Round(Avg('price_per_gallon'),3)),
'total_spent': sum_total_sale(Fillup.objects.filter(username=self.request.user)),
'avg_mpg': avg_mpg(Fillup.objects.filter(username=self.request.user))
}
context['stats'] = overview_stats
context['active_cars'] = Car.objects.filter(status='Active').filter(username=self.request.user)
context['last_10_fillups'] = Fillup.objects.filter(username=self.request.user).order_by('-date')[:10]
return context
For now, everything in the overview_stats dict is what I originally had when I was just querying stuff for the logged-in user, where there was just a simple "myprofile" url. The problem I'm having her is that the get_object_or_404 isn't finding the user. I know that username=self.kwargs.get("username") is getting 'seconduser' like it should be, but for some reason I just can't get the user.
For some extra info, here is one of my models:
class Car(models.Model):
username = models.ForeignKey(User,on_delete=models.CASCADE)
name = models.CharField(max_length=25)
make = models.CharField(max_length=25)
model = models.CharField(max_length=25)
model_year = models.IntegerField(choices=MODEL_YEARS)
status = models.CharField(max_length=10,choices=STATUS,default='Active')
def __str__(self):
return self.name
And in the initial Django tutorial I did, the instructor said it is best to extend the user model so it's easier to make changes, so I have this in a separate app, accounts/models.py
class User(auth.models.User,auth.models.PermissionsMixin):
def __str__(self):
return "#{}".format(self.username)
I've tried using the method in this question which is why I have the slug field stuff in my view currently, and while my question is essentially a duplicate of this question
I've been stuck on this all night and would really appreciate any help, thanks!
Remove the the self from self.kwargs.get("username"). It should be kwargs.get("username").
kwargs is an argument not on object property.
I am trying to extend the user model using a one to one relationship to a UserProfile model. I added some boolean fields and in the view I am trying to use those fields as permissions.
Here is my model:
class UserProfile(models.Model):
user = models.OneToOneField(User)
FirstName = models.CharField(max_length=25)
LastName = models.CharField(max_length=25)
ProximityAccess = models.BooleanField(default=True)
NewProxAccess = models.BooleanField(default=False)
def __unicode__(self):
return self.user.username
and here is the view I am trying to use:
#login_required
def NewProx(request):
if UserProfile.NewProxAccess:
if request.method == 'POST':
form = ProxForm(request.POST)
if form.is_valid():
ProxPart_instance = form.save(commit=True)
ProxPart_instance.save()
return HttpResponseRedirect('/proximity')
else:
form = ProxForm()
return render(request, 'app/NewProx.html', {'form': form})
else:
raise PermissionDenied
I don't get any error messages but it does not work as intended. I was hoping that if the user profile had NewProxAccess set to False it would raise the PermissionDenied but it doesn't. I have the admin module wired up and I can select or deselect the checkbox for that field but it has no effect. If I comment out the rest I can get it to show the Permission Denied error so it has to be in the view (I think). I think I am missing a line the establishes the logged in user as the user instance so we can check to see if the user has the permission or not. I know there are a ton of ways to do this and there is probably a better way but for the sake of learning, what is it that I am missing for this to work?
Thanks
Scott
As you want to check access for particular profile but not UserProfile model you need to do:
if request.user.userprofile.NewProxAccess:
# your code
As a note: according to PEP8 best practices you should use camelCase only for naming Classes. For attrs, functions use underscore: my_function
I am pretty sure that question have been covered but I tried everything I found related on the stack to solve my problem without any success.
Here is my model.py
from django.db import models
from django.contrib.auth.models import User
class Description(models.Model):
titre_routine = models.CharField(max_length=100)
type_routine = models.CharField(max_length=100)
lieu_routine = models.CharField(max_length=100)
periode = models.CharField(max_length=100, blank=True)
routine_description = models.TextField()
raison_chgmt = models.TextField(blank=True)
user = models.ForeignKey(User)
#prendre une liste de routine et faire une liste de choix
#si non ajout a la liste de routine dans nouvelle table
def __unicode__(self):
return self.titre_routine
#User.profile = property(lambda u: Description.objects.get_or_create(user=u)[0])
class Rewards(models.Model):
jour = models.CharField(max_length=4)
activite_altr = models.TextField()
first_word = models.CharField(max_length=100, blank=True)
second_word = models.CharField(max_length=100, blank=True)
third_word = models.CharField(max_length=100, blank=True)
urge_validator = models.BooleanField() #urge satisfait ou non?
urge_validate_date = models.DateField(auto_now=True)
description = models.ForeignKey(Description)
def __unicode__(self):
return self.jour
To explain my model:
One "user" can create multiple "description".
a "description" contain multiple "rewards".
...
a "description" contain multiple "other things"
here is my view.py
#the user access to his dashboard, dashboard filtered by the user logged in.
#login_required(login_url='/userauth/login/')
def dashboard(request):
routine_info = Description.objects.filter(user=request.user)
return render_to_response('dashboard.html', {
'routine_info': routine_info
})
#dashboard for the reward section that should be like the first dashboard filtered only by the user logged in AND should only show the reward related to One description created previously by the user.
#login_required(login_url='/userauth/login/')
def reward_dashboard(request):
reward_info = Rewards.objects.all()
return render_to_response('rewards_dashboard.html', {
'reward_info': reward_info
})
#the user can have access to the reward he created as the other def, he should be the only one to have access (again reward created by the user and related to ONE description)
#login_required(login_url='/userauth/login/')
def reward_description(request, reward_id):
reward_info = Rewards.objects.get(id=reward_id)
return render_to_response('reward_description.html', {
'reward': reward_info
})
#rewardform, I miss the pieces to obtain de description id that will link both table Rewards and Description (and recursively the User table via Description???)
#login_required(login_url='/userauth/login/')
def new_reward(request):
if request.POST:
form = RewardsForm(request.POST)
if form.is_valid():
obj = form.save(commit= False)
obj.description = #Missing piece
#test that could never happen
if obj.description == 200:
return HttpResponseRedirect('/reward_dashboard/')
else:
obj.save()
return HttpResponseRedirect('/reward_dashboard/')
else:
form = RewardsForm()
args = {}
args.update(csrf(request))
args['form'] = form
return render_to_response('create_reward.html', args)
to recapitulate:
-the user can have access to the reward he created as the other def he should be the only one to have access (again reward created by the user and related to ONE description)
-dashboard for the reward section that should be has the first dashboard filtered only by the user logged in AND should only show the reward related One description created previously.
-rewardform, I miss the pieces to obtain de description id that will link both table Rewards and Description (and recursively the User table via Description)
Should I try to look into session? (when a user create a description I assign him a specific Id?)
I just hope I made my case clear in my explanation.
Thank for your help!
You have multiple choices to solve this. It really depends on the workflow you want to implement in your app.
Choose a description before creating the reward
You will have a page listing all the user's descriptions : this is your dashboard view.
From here, the user will have to choose the description he wants to add a reward to : by clicking on it, by click on a specific create reward link, etc. This is up to you (ergo/design choice). The main point is to get the description id and pass it to your new_reward view via URLconf (urls.py) with something like this :
# urls.py
urlpatterns = patterns('',
# your urlpatterns ...
url(r'^description/(?P<description_id>\d+)/reward/add/$', 'your.app.views.new_reward', name='add-reward'),
# your urlpatterns ...
)
This is just an example. You can set the URL as you want. Anyway, when your user will click on the link or the button (or whatever you choosed) the URL will be something like mondomaine.fr/yourappprefix/description/4/reward/add (where 4 is the id of the chosen description - it will be up to you to generate such a link)
With that, in your view, you can retrieve the description object :
#rewardform, I miss the pieces to obtain de description id that will link both table Rewards and Description (and recursively the User table via Description???)
#login_required(login_url='/userauth/login/')
def new_reward(request, description_id):
description = Description.objects.get_or_404(id=description_id)
pass
Create a reward with a direct link
If such a workflow is not even an option in your case, then smply add a description field to your RewardsForm. With that, your user will be able to aim the description he wants to add a reward to.
To limit the descriptions list to only the ones created by the current user, you could do this in your views.py :
# your code ...
else:
form = RewardsForm()
form.fields['description'].queryset = Description.objects.filter(user=request.user)
Maybe you will have to do the save if form.is_valid returns False.
I hope that helps you at least.
In my models.py
I only added a ForeignKey to link my other table (Rewards to User), to be able to get the current id of the current logged user (to restrict the description that do not belongs to the user)
user = models.ForeignKey(User)
in my urls.py
url(r'^reward_dashboard/(?P<routine_id>\d+)/$', reward_dashboard),
url(r'^reward_create/(?P<description_id>\d+)/$', new_reward),
url(r'^reward_description/(?P<reward_id>\d+)/$', reward_description),
in my views.py:
#login_required(login_url='/userauth/login/')
def reward_dashboard(request, routine_id): (get the routine_id to only get the dashboard related to the description)
d = Description.objects.get(id=routine_id) #give the object with description related)
reward_info = Rewards.objects.filter(user=request.user).filter(description=d) #filter with the right description (from the routine_id passed in) and the right user logged.
return render_to_response('rewards_dashboard.html', {
'reward_info': reward_info,
'd' : d #i will in my template render the d.id in a link {{reward_id}}
})
#login_required(login_url='/userauth/login/')
def reward_description(request, reward_id):
reward_info = Rewards.objects.filter(user=request.user).get(id=reward_id)
return render_to_response('reward_description.html', {
'reward': reward_info
})
#login_required(login_url='/userauth/login/')
def new_reward(request, description_id):
#description = Description.objects.get_or_404(id=description_id)
d = Description.objects.get(id=description_id) #get all object with the description_id
if request.POST:
form = RewardsForm(request.POST)
if form.is_valid():
obj = form.save(commit= False)
obj.description = d #fill the form with the id that come from my template link
obj.user = request.user #fill the user with the logged one
if obj.description == 200:
return HttpResponseRedirect('/dashboard/')
else:
obj.save()
return HttpResponseRedirect("/reward_dashboard/%s" % d.id)
else:
form = RewardsForm()
args = {}
args.update(csrf(request))
args['form'] = form
args['d'] = d
return render_to_response('create_reward.html', args)
I hope my explanation would help.
ps: I will take a look to the get_or_404() to understand how it work because for now i don't see now the difference in result but for sure would be interesting to redirect the user who try to have access he do not belong.
I am unable to upload the file. I am getting
Type error builtin_function_or_method' object is not iterable
models.py
class seeker(models.Model):
user = models.OneToOneField(User)
birthday = models.DateField()
class Upload(models.Model):
user = models.ForeignKey(Seekers)
resume = models.FileField(upload_to ='resume', blank = True, null = True)
forms.py
class SeekersForm(forms.Form):
resume = forms.FileField(label = 'Select a file',help_text = 'max.3 MB')
views.py
def List(request):
# Handle file upload
if request.method == 'POST':
form = SeekersForm(request.POST, request.FILES)
if form.is_valid():
#id = User.object.get(id)
newdoc = Seekers.objects.get(user_id)
newdoc.resume =Upload(resume = request.FILES['resume'])
newdoc.save()
#seekers_edit = Seekers.objects.get(id)
#seekers_edit.resume = Seekers(resume = request.FILES['resume'])
#seekers_edit.save()
#Redirect to the document list after POST
return HttpResponseRedirect('/profile/')
else:
form = SeekersForm() # A empty, unbound form
#Load documents for the list page
seekers = Seekers.objects.all()
#Render list page with the documents and the form
return render_to_response('list.html',{'seekers':seekers,'form':form},context_instance=RequestContext(request))
It's hard to say where your problem is, but I think the following line of code is the main problem:
newdoc.resume =Upload(resume = request.FILES['resume'])
You have to save a file in a FileField explicitly before you save the entire model instance. Also, if you have a ForeignKey field in one of your models and you want to assign it an instance of another model, please save that instance first before you do the assignment. Without knowing your Seekers model, all I can do is guessing what might help you. Something like the following might get you started:
your_file = request.FILES['resume']
upload_instance = Upload()
upload_instance.resume.save(name=your_file.name, content=your_file, save=False)
upload_instance.user = ... # Here goes an instance of your Seekers model
upload_instance.save() # Here you save the whole instance of your Upload model
Also, please note the following:
Your model Seekers should rather be named Seeker using the singular, not the plural. This should generally be like that with all your models.
Python functions should always start with a lowercase letter, i.e. list instead of List. However, this name is a bad choice here anyway, because a function called list is already present in Python's standard library.
Please take a closer look at Django's documentation. It's all in there what you need to know. I recommend you to read especially these sections:
https://docs.djangoproject.com/en/1.4/ref/models/fields/#filefield
https://docs.djangoproject.com/en/1.4/ref/files/file/
Problems in your code:
Your form definition duplicates information from your model — just use forms.ModelForm (with exclude so as not to display the user field)
As currently pasted, newdoc = Seekers.objects.get(user_id) will raise a TypeError ('foo' object is not iterable); .get() accepts keyword parameter filters, not anything else.
Accessing request.FILES['resume'] manually isn't necessary or recommended
So, in short, you're almost there; just let Django forms do more of the work for you:
# forms.py
class SeekerForm(forms.ModelForm)
class Meta:
model = Seeker
# views.py
def seeker_list(request):
# Opinions are divided as to whether it's ever appropriate to
# modify the database like this on a GET request, but it seems
# to make sense here
seeker = Seekers.objects.get_or_create(user=request.user)
if request.method == 'POST':
form = SeekerForm(request.POST, request.FILES, instance=seeker)
if form.is_valid():
form.save()
return HttpResponseRedirect('/profile/')
else:
form = SeekerForm(instance=seeker)
seekers = Seekers.objects.all()
#Render list page with the documents and the form
return render_to_response('list.html', {
'seekers':seekers,
'form':form
}, context_instance=RequestContext(request))
It's not clear what the significance (if any) of the commented-out sections of your code is — I've assumed you always want to modify the current user's Seeker, but if not then adapt as appropriate.
Model:
class ProjectType(models.Model):
project_type_id = models.AutoField(primary_key=True)
name = models.CharField(max_length=45, help_text='Type of project', verbose_name='Project Type')
slug = models.SlugField(max_length=45, blank=True)
description = models.CharField(max_length=400, help_text='Description of the main purpose of the project', verbose_name='Project Type Description')
default = models.BooleanField(default=False)
owner = models.ForeignKey(User)
class Meta:
...
unique_together = (('slug', 'owner'),('name', 'owner'))
I need a form to create/update ProjectType's. Please note the owner field - it is supposed to be current logged-in user. The question is how to ensure that constraints in the unique_together are validated correctly.
I do not want to show owner field on the form - it's the current user, so it should be set automatically by the system. But no matter how I try to do this, either validation does not work, or there are other errors.
Among approaches I tried (individually or in combination):
Creating a hidden field in the related ModelField
Defining init in ProjectTypeForm (in various ways), for example:
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(ProjectTypeForm, self).__init__(*args, **kwargs)
self.fields['owner'].initial = self.user
Setting values in the view like:
...
if request.method == 'POST':
project_type = ProjectType(owner=request.user)
form = ProjectTypeForm(request.POST, instance=project_type, user = request.user.pk) # also tries w/o pk
...
Overriding clean() method of the form in various ways, along these lines:
def clean(self):
cleaned_data = super(ProjectTypeForm, self).clean()
slug=cleaned_data.get('slug')
owner = cleaned_data.get('owner')
if slug:
user = User.objects.get(pk=owner)
...
Many of these approaches are based on various answers found on stackoverflow.com. However, no matter what I try, I cannot find a way to accomplish what I need: (1) auto-setting of the owner field and (2) validation for uniqueness: owner/type_name and owner/type_slug. Typical errors I have is that (a) owner is not recognized as a User (it's treated as a PK), (b) incorrect validation (like lack of it or it misses the fact that it's the same record being edited, etc.), (c) owner is a required field.
For the record - if the owner is a regular field in the form, everything works as expected, but I cannot allow users to set the owner value.
Is there any, hopefully elegant, solution to this?
Thanks!
Exclude the owner field from your form, and save the user in your form's init method - then you can use it to validate the form, eg
class ProjectTypeForm(...):
...
def __init__(self, user, *args, **kwargs):
super(ProjectTypeForm, self).__init__(*args, **kwargs)
self.user = user
def clean(self):
user_projects = ProjectType.objects.filter(owner=self.user)
if user_projects.filter(slug=self.cleaned_data['slug']):
raise forms.ValidationError('...')
elif user_projects.filter(name=self.cleaned_data['name']):
raise forms.ValidationError('...')
else:
return self.cleaned_data
Then in your view, do something like this when creating a new ProjectType:
if request.method == 'POST':
form = ProjectTypeForm(request.user, request.POST)
if form.is_valid():
ptype = form.save(commit=False)
ptype.owner = request.user
ptype.save()
You shouldn't need that to save existing ProjectType objects though.
As I mentioned in my comment, one possible solution is essentially to go along with Django forms and use the owner field on the form. So, what I've done is modified init in this way:
def __init__(self, user, *args, **kwargs):
super(ProjectTypeForm, self).__init__(*args, **kwargs)
self.fields['owner'] = forms.ModelChoiceField(
label='Owner*',
queryset=User.objects.filter(username=user.username),
help_text="Project types are unique to logged-in users who are set as their owners.",
required=True,
empty_label=None)
Basically, what it does it is still using ChoiceField but sets it to one option - current user. In addition, empty_label=None ensures that there is no "empty" choice. The effect is (since username is unique) that current user name appears visible and is the only choice in the otherwise dropdown list with more choices.
In the view I follow this approach:
...
if request.method == 'POST':
project_type = ProjectType()
form = ProjectTypeForm(request.user,request.POST, instance=project_type,)
if form.is_valid():
project_type.save()
return HttpResponseRedirect(reverse('project_types'))
else:
form = ProjectTypeForm(request.user)
...
Basically, that's it - validation of unique constraints (and the whole thing) works like a charm.
Do I like this solution? No. I consider it a hack (ironically, even if it goes along with standard Django approaches). But it requires something that is totally unnecessary. One benefit of this approach is that it clearly communicates to the current user that s/he is set as the project type owner. But even with this in mind I would rather show a message (instead of a field) that Current user X will be set as the owner of the project type being created. So, if someone has a better solution, please submit it to illustrate the full power and flexibility of Django.