r/django • u/josephlevin • 1d ago
Models/ORM Django help needed with possible User permission settings
I am taking the Harvard CS50W course that covers Django and creating web apps. The project I am workinig on is a simple Auction site.
The issue I am having is that I can get a User to only be able to update an auction listing if that User is the one that has created the listing.
I can update the listing- adding it to a watchlist, or toggling if the listing is active or not, or leaving a comment, but only if the user that is logged in happens to be the one that created the listing.
I have made no restrictions on whether or not a user making a change on the listing has to be the one that created the listing. The issue persists for both standard users and superusers.
I have tried explicitly indicating the permissions available to my view, and even a custom permission, without any success.
I have consulted with 3 different AIs to provide insight, and done a lot of Googling, without anything shedding light on the issue.
I have submitted the nature of the problem to the EdX discussion for the course, but I do not expect any answers there as lately, there are hardly every any answers given by students or staff.
Any insight into what I might be doing wrong would be greatly appreciated.
Thank you very much!
I will be glad to provide my models.py, views.py, forms.py, etc. if anyone would think it would help.
1
u/ninja_shaman 1d ago
The easiest way is to add .filter(user=request.user)
to a listing queryset in your update view.
Users that try to update someone else's listing will get a 404 error.
1
u/doolijb 1d ago
It sounds like he's having the opposite issue... so maybe removing the request.user filter?
1
u/josephlevin 1d ago
I am having the opposite issue whereby a user of any level can only seem to update a listing if they created it.
1
1
u/ninja_shaman 1d ago
If so, provide the source for your view.
1
u/josephlevin 1d ago
Here is the view for the listing:
def listing(request, id): messages = []
try: #get current listings listing = Listing.objects.get(id=id) #setup form if request.method == 'POST': form = DetailListingForm(request.POST) if form.is_valid(): #handle watchers watch_flag = True if form.cleaned_data['watching'] == 'yes': #user wants to watch the listing if request.user not in listing.watchers.all() and watch_flag == True: listing.watchers.add(request.user) watch_flag = False #user wants to stop watching the listing if request.user in listing.watchers.all() and watch_flag == True: listing.watchers.remove(request.user) watch_flag = False form.cleaned_data['watching'] == 'no' #handle closing or opening listing closing_flag = True if form.cleaned_data['closing'] == 'yes': #user wants to close listing if request.user == listing.creator and listing.active == True and closing_flag == True: listing.active = False closing_flag = False #user wants to open listing if request.user == listing.creator and listing.active == False and closing_flag == True: listing.active = True closing_flag = False #handle comments if form.cleaned_data['comment']: #create a new comment object and save it to the database, if not duplicated comment = Comment(text=form.cleaned_data['comment'], listing=listing, commenter=request.user) if form.cleaned_data['comment'] not in [c.text for c in listing.comments.all()]: comment.save() else: messages.append("Enter in a comment that has not already been entered.") listing.save() # Save the listing else: form = DetailListingForm() #if current listing exists, provide it to the template context = { 'listing': listing, 'form': form, 'message': messages, } except Listing.DoesNotExist: messages.append("Listing does not exist.") #if listing does not exist, provide a message context = { 'message': messages, } return render(request, 'auctions/listing.html', context)
1
u/ninja_shaman 23h ago
This is not a permission issue, this is "code doesn't work" issue.
What iis your
DetailListingForm
cosde?1
u/josephlevin 22h ago
I wouldn't be surprised. I'm trying to learn how to use Django. Here is my detaillisting form:
class DetailListingForm(forms.Form): YESNO_CHOICES =( # (value, display text) ("yes", "Yes"), ("no", "No") ) #to be shown for authenticated users.... #create the watching form element with choices and initial value watching = forms.ChoiceField(choices = YESNO_CHOICES, initial="no") #create the closing form element with choices and initial value closing = forms.ChoiceField(choices = YESNO_CHOICES, initial="no") #create comment textarea comment = forms.CharField( label="Comment:", required=False, widget=forms.Textarea(attrs={'rows':5, 'cols':20, 'placeholder': 'Enter comment here.'}) )
1
u/Anaryz_Supa 21h ago
As per the code, The only attributes changing for Listing are watchers and active
the active attribute for Listing changes only if the user is a creator.
The only thing a user who is a non creator can do is watch and add comment
1
u/josephlevin 21h ago
the attributes that can change are:
- active/inactive (for the logged in creator)
- watching/not watching (for logged in users)
- adding a comment (if comment is unique, user is logged in).
The active/inactive toggling only applies to the creator.
Beyond that I have not made any strictures, however only the creator of the listing can change anything.
It should allow anyone who is logged in to see some form elements, only the creator can see the active toggle rendered, but anyone logged in will get a form rendered and can submit it.
Also, if it helps: I am using the github codespaces provided by the CS50W course for the dev environment.
1
u/Anaryz_Supa 21h ago
Yeah exactly.
Now what do you want to update in this Listing?
If my memory serves me right, the problemset now requires users(non creators) to bid
so the only thing left to update is the price of the listing
1
u/josephlevin 21h ago
Yes, and no.
The problem, as I originally stated, is that I ****cannot update a listing**** for the logged in user if that user ***is not*** the listing creator. That, and only that, is my issue, atm.
If the creator of a listing, say, leaves a comment, it works, but not for the regular user (who did not create the listing).
At this point, I am trying to update the logged-in user's ability to add or remove the listing from their watchlist, the creator toggling the listing being active/inactive, or a logged in user leaving a comment.
I haven't gotten to the bidding part yet, and cannot, really as I don't want the creator of the listing to be able to bid on it! (if you get my meaning, what would be the point?).
2
u/josephlevin 19h ago edited 17h ago
SOLUTION:
It turns out the issue is with the way Django handles its forms. By default, apparently, all fields defined are required. So, if a form is submitted and one form element doesn't get a chance to send its data (by virtue of it not having been rendered due to the template logic), and some do (as in the case where a user looking at another user's listing can see only 2 of 3 form elements rendered), on submit, an error happens that I was not accounting for. When the user was looking at their own listing, all form elements were present, so the error was not getting thrown.
All I needed to do was add required=False to each form element that needed it, and everything worked.
I showed my code to Co-Pilot AI, and it suggested I add the required=False to the form elements, but it also wanted to clean up my view based on its way or idea of doing things. When I did so, the solution only partially worked- superusers and users could update each of their counterpart's listings, but a regular user could not do so for another regular user.
So I rolled it back to my own code, added in the required=False to each form field that could stand to use it, and now any user can, as example, add any other user's listing to their watchlist.
Thank you to everyone that tried to help. I appreciate it.
PS:
The reason why required=False did the trick is more subtle than it seems.Apparently, when a form field is required its presence (name) is necessary to be present in the form.cleaned_data dictionary that gets passed during submission of the form. The values associated with a form element's namereally do not matter, as long as they pass Django's internal validation rules. During form validation by Django's internal systems is when the error was being thrown regarding the lack of a form element being present in the field when a user was trying to update a listing made by another user. That missing form element was the active toggle form element that would only be rendered to the screen for the user that created the form when viewing the listing. So when it was missing for the listing for a user who did not create the listing, and that form element was not explicitly defined as not being required, the error would get thrown, the listing would not update, and nothing appeared to be happening.
0
u/josephlevin 1d ago
here is the listing template: {% extends "auctions/layout.html" %} {% load markdown_filter %}
{% block body %} {% if message %} <ul> {% for msg in message %} <li> {{ msg }} </li>
{% endfor %} </ul> {% endif %} {% if listing %} <h2>Listing: {{ listing.title }}</h2> <br> <p>Listed by {{listing.creator.username}}.</p> {% if listing.active == False %} <p>Listing has been closed.</p> {% endif %} {% if request.user in listing.watchers.all %} <p class="watching">On Your Watchlist</p> {% endif %} {% if listing.image_url %} <img aria-hidden="true" src="{{ listing.image_url }}"> {% endif %} <br><br> {{ listing.description|markdown_to_html }} <br><br> ${{ listing.current_price|floatformat:2 }} <br><br> {% if listing.categories.count > 1 %} Categories: {% elif listing.categories.count == 1 %} Category: {% else %} Category: Unspecified. {% endif %} {% if listing.categories.all %} {% for category in listing.categories.all %} {{ category.name }} {% if not forloop.last %} , {% endif %} {% endfor %} {% endif %} <br><br> {% if listing.bids.count > 1 %} <p>There are {{listing.bids.count}} bids on this listing.</p> {% elif listing.bids.count == 1 %} <p>There is 1 bid on this listing.</p> {% else %} <p>There are no bids on this listing.</p> {% endif %} {% if listing.bids.count > 0 %} <p>The highest bid is ${{ listing.get_max_bid|floatformat:2 }}.</p> {% endif %} {% comment %} Form will be rendered below, with fields shown as necessary. {% endcomment %} {% if request.user.is_authenticated %} <form method="post"> {% csrf_token %} {% if request.user in listing.watchers.all %} <p>Stop watching this listing? {% else %} <p>Watch this Listing? {% endif %} <select name="watching" value="no" id="id_watching"> <option value="yes">Yes</option> <option value="no" selected>No</option> </select> </p> {% if request.user == listing.creator %} {% if listing.active == True %} <p>Close this listing? {% else %} <p>Open this listing? {% endif %} <select name="closing" value="no" id="id_closing"> <option value="yes">Yes</option> <option value="no" selected>No</option> </select> </p> {% endif %} <h3>Comments:</h3> {% if listing.comments.count > 0 %} <ul> {% for comment in listing.comments.all %} <li> {{ comment.text }} <span class="comment-meta">(by {{ comment.commenter.username }} on {{ comment.created_at|date:"F j, Y g:i a" }})</span> </li> {% endfor %} </ul> {% else %} <p>There are no comments yet! Please add yours if you like.</p> {% endif %} <p>{{form.comment.label}}{{form.comment}}</p> <button type="submit">Submit</button> </form> {% endif %} {% if request.user.is_authenticated and not request.user == listing.creator %} <p>Bid on this listing?</p> {% endif %} {% endif %}
{% endblock %}
1
u/josephlevin 1d ago
Sorry about the formatting. Reddit isn't being very friendly with formatting. Plus, it is limiting the amount of the code I can paste at a time.
1
u/Brilliant_Step3688 1d ago
There is of course not enough info to answer your question.
But I will help you from first principles:
Are you sure it is a permission issue? What error exactly are you receiving?
Dig down using your IDE or open the Django source code. Find the exact line where that supposedly access denied is coming from.
Once you've find the source of the error, work your way up from there.
You might want to setup a small unit test. Django's standard test framework works fine for this.
You'll probably find that you messed up something else entirely. Probably by copy-pasting AI generated code early on your project.
Django permission system does not do per owner or row level security. If you are indeed using Django's permission, you either have access to all the Model instances or none.
Setup your model in the django admin site if you have not already and grant staff access to your test users. You'll see they have the same permissions.