So in my application the user can use their items and it changes certain things in the database, however most actions would require different code.
An example would be an item could be used to add a month to the users account, another would change a variable on their profile.
I'm thinking the best was to do this is writing the code for each item use case (I guess you could use an eval and store in the DB but thats nasty). Heres what I have in my views
try:
profile = request.user.profile
inventory_item = Inventory.objects.get(id=inventory)
if inventory_item.user == request.user:
# some code here to use the item??
useitem = ItemUse(inventory_item.item)
else:
messages.error(request, 'You cannot use an item you do not have')
return HttpResponseRedirect(reverse('item_inventory'))
except Inventory.DoesNotExist:
messages.error(request, 'No such item')
return HttpResponseRedirect(reverse('item_inventory'))
How would I be able to write some decent stable code? Any suggestions would be much appreciated (i've not written the ItemUse bit, thats what i'm asking the advice on for)
I've worked out a solution, not sure if it makes much sense or is very scalable but here's what i've come up with:
class ItemUse:
def __init__(self, item, user):
self.item = item
self.user = user
def execute(self):
try:
getattr(self, self.item.slug)()
Inventory.objects.remove(self.user, self.item, 1)
return True
except:
return False
def foo(self):
self.user.profile.foo += 100
self.user.profile.save(update_fields=['foo'])
return True
This way I can parse the item and user and then test the execute method. Each method will correspond to the item slug value. It will do for now I guess
Related
I've got django model and view implemented like here: (+mysql db)
class MyModel(models.Model):
name = models.CharField(max_length=100)
version = models.IntegerField(default=1, editable=False)
def updateModel(request, id):
toUpdate = MyModel.objects.get(pk=id)
if request.method=='POST':
form = MyModelForm(request.POST, instance=toUpdate)
if form.is_valid():
actual = MyModel.objects.get(pk=id)
if (actual.version == form.instance.version):
form.instance.version = form.instance.version+1
form.save()
return redirect('somewhere')
else:
#some error
form = MyModelForm(instance=toUpdate)
return render(request, 'somwhere2/createupdate.html', {'form':form})
The scenario is: - current model values: name="aaa", version=1,
2 users open edit form, first user changes name "aaa" to "bbb", and saves, second changes name "aaa" co "ccc" and saves. Result is "ccc", but I'd like to have some message/version conflict message... The problem is.. there is no conflict, because even if the second user can see still "aaa", while in DB there is "bbb" already... but after POST button click, the values are updated to bbb first, and version is updated, so the code is unable to see, that user2 works on old version... :(
I'd like that versioning mechanism to prevent such scenario, but I'm unable to achieve it...
How to implement it?
I've read everything I could about django optimistic locking etc, but unable to achieve it,
I think using select_for_update() might solve your problem.
Check out this article.
I was thinking of something like this:
def updateModel(request, id):
toUpdate = MyModel.objects.get(pk=id)
if request.method=='POST':
form = MyModelForm(request.POST, instance=toUpdate)
if form.is_valid():
with transaction.atomic():
try:
actual = MyModel.objects.filter(pk=id).select_for_update(nowait=True).get()
except OperationalError:
# raise some error
if (actual.version == form.instance.version):
form.instance.version = form.instance.version+1
form.save()
return redirect('somewhere')
else:
#some error
form = MyModelForm(instance=toUpdate)
return render(request, 'somwhere2/createupdate.html', {'form':form})
I believe I've found a problem. It's here:
in Model: version =(...) editable=False
So when field is not editable - it is not placed in form, so you're loosing information about version number... And are unable to compare initially loaded version, with actual version.
it is still not thread-safe, but in general- blocks typical attempts to edit and save form by 2 users.
I have a task model like
class Tasks(models.Model):
made_by=models.ForeignKey(User , on_delete=models.CASCADE)
title = models.CharField(max_length=100,null=True,blank=True)
I have a view like
def get(self, request, id):
task = Tasks.objects.get(id=id)
if task:
serializer = TasksSerializer(task, many=False)
return Response(success_response(serializer.data, "Contact Information."
status=status.HTTP_200_OK)
And a PUT in same way. I want to check Only User which made_by it can access it . Is there any smart way to do this ? I dont want to query for check again in all views and hereafter.
Since it appears that you are using class-based views I would suggest that you override the dispatch method of your class. This class gets executed every time someone calls the view, no matter the method.
In this dispatch method you could first of all retrieve the task object like you do in the get-function in your current code. After that step you could then perform a check to see whether the request.user equals the object's made_by.
For example:
def dispatch(self, request, id):
self.task = Tasks.objects.get(id=id) # consider using get_object_or_404
# Check if user is owner of task, otherwise throw a 404
if request.user != self.task.made_by:
raise Http404()
# Will continue execution as normal, calling get() if a get-request was made
# the variable self.task will be available in this function, so re-retrieving the
# object is not necessary
return super().dispatch(request, id)
Additionally I would also suggest using the default LoginRequiredMixin (source) to make sure that only logged-in users can access the view. It could eliminate custom written checks in many cases.
The PermissionRequiredMixin (source) is also a great choice when dealing with more general permissions that are not related to specific instances.
For more specific - customized - permissions you could also use the UserPassesTestMixin (source) to write checks in dedicated test funcs to keep your code cleaner.
a couple different ways to go about this but I think the easiest is to test the user's made by status. i.e.
def run_task(self, request, id):
if request.user.made_by == 'Foo Bar'
. . . .
return Response(success_response ...
else
return Response(failure_response ...
another, more complicated way would be to play around with the standard account model perms and set 'task' perms to false. i.e.
def has_task_perms(self):
return False
and then in the process of user creation through whichever user type has the ability to give task perms. The most common way I am aware of to do this is to use an account manager (a whole rabbit whole in and of itself.) i.e.
class AccountManager(BaseUserManager):
def create_user(self, password):
# define regular creation process
. . .
return user
def create_special_user(self, password):
# define special creation process i.e.
has_task_perms == True
return user
I tried so many solution but At the End I made a decorator like this
def task_ownership_check(func):
def wrapper(request,*args, **kwargs):
print(kwargs['id'])
try:
task = Tasks.objects.get(id=kwargs['id'])
except Tasks.DoesNotExist:
return Response(failure_response(data={}, msg='No task matching query found'),
status=status.HTTP_404_NOT_FOUND)
if args[0].user != task.todolist.for_user:
return Response(failure_response(data={'error': 'You are not allowed to access this record'},
msg='You are not allowed to access this record'),
status=status.HTTP_400_BAD_REQUEST)
else:
return func(args[0], kwargs['id'])
return wrapper
So Now I can check easily by including #task_ownership_check on any task
I'm still struggling with django-filter. I have my filter defined below
class MasterListFilter(django_filters.FilterSet):
project = django_filters.ModelChoiceFilter(
label='Projects',
name='project_fkey',
queryset=Project.objects.filter(deleted__isnull=True)
)
class Meta:
model = Task
fields = ['project']
#property
def qs(self):
parent = super(MasterListFilter, self).qs
user = get_current_user()
return parent.filter(master=True, deleted__isnull=True, user_fkey=user.id)
This works perfectly fine. However I also want to filter the dropdown filter (ie the Project queryset) by the current user. As my user is logged in and authenticated, I believe the user details should be attached to the request.
According to the django-filter docs
The FilterSet may be initialized with an optional request argument. If
a request object is passed, then you may access the request during
filtering. This allows you to filter by properties on the request,
such as the currently logged-in user or the Accepts-Languages header.
So it would seem that the request is there, but I can't work out how to access it as an argument of the FilterSet, nor have I been able to find any examples in the docs or anywhere else in my travels as to how to do it. So if anyone can give me any clues at all, I really would appreciate the help.
Edit
Thanks Willem for the information and advice. Very much appreciated. However I think I may not have explained myself clearly. The problem is not filtering the qs
#property
def qs(self):
parent = super(MasterListFilter, self).qs
user = get_current_user()
return parent.filter(master=True, deleted__isnull=True, user_fkey=user.id)
this bit works fine although I will change it to use the self.request.user as suggested along with capturing any requests that are None. This portion returns my results table that gets rendered in my hmtl page. In this case it is a list of tasks that belong to various projects. What I want to be able to do is give the users a dropdown list at the top of the page which has a list of projects that they can choose from and thereby filter the results table by individual projects. (Projects being the parent model.) This part of the code:
class MasterListFilter(django_filters.FilterSet):
project = django_filters.ModelChoiceFilter(
label='Projects',
name='project_fkey',
queryset=Project.objects.filter(deleted__isnull=True)
)
does achieve this to a point in that it gives a list of all projects that have, in this case, not been deleted. Unfortunately the users are able to create their own projects, each of which has a foreign key back to the user who created it. Therefore, in addition to displaying projects that have not been deleted, I also want to show only the projects that belong to the current user.
No doubt I am missing something here, but my understanding is that django_filters.FilterSet has the request as a property, but if I try to use 'user = self.request.user' in this part of the class, I get an error saying self is not defined (and looking at it, it clearly isn't.) Frankly I'm now a bit stumped and really need some advice on this part of the code.
In short: you can access the request with self.request. If no request is given, then self.request is None.
The request is an attribute of the self. So you can obtain this with self.request.user:
#property
def qs(self):
parent = super(MasterListFilter, self).qs
user = self.request.user # unsafe (!) since request can be None!
return parent.filter(master=True, deleted__isnull=True, user_fkey=user.id)
Note however that the request can be None. So it is better to guard against that, like:
#property
def qs(self):
parent = super(MasterListFilter, self).qs
if self.request:
user = self.request.user
else:
user = None
if user and user.is_authenticated():
return parent.filter(master=True, deleted__isnull=True, user_fkey=user.id)
else:
# do something if no request, or no logged in user
# for example
return parent.filter(master=True, deleted__isnull=True)
Or in a more compact form:
#property
def qs(self):
parent = super(MasterListFilter, self).qs
filters = dict(master=True, deleted__isnull=True)
user = getattr(self.request, 'user', None)
if user and user.is_authenticated():
filters['user_fkey'] = user.id
return parent.filter(**filters)
Since obtaining the user is a rather common operation, we can implement a mixin for this:
class UserFilterMixin(object):
#property
def current_user(self):
return getattr(self.request, 'user', None)
You can then use the mixin, and thus obtain the user with self.current_user.
To filter your list of projects by the request.user, you need to provide a callable as the queryset argument. I'm not familiar with your project, but the code should look something like:
def requested_projects(request):
if request is None:
return Projects.objects.none()
return Project.objects.filter(deleted__isnull=True, user_fkey=request.user)
class MasterListFilter(django_filters.FilterSet):
project = django_filters.ModelChoiceFilter(
label='Projects',
name='project_fkey',
queryset=requested_projects,
)
...
Update: The solution can be found as a separate answer
I am making a Django form to allow users to add tvshows to my db. To do this I have a Tvshow model, a TvshowModelForm and I use the generic class-based views CreateTvshowView/UpdateTvshowView to generate the form.
Now comes my problem: lets say a user wants to add a show to the db, e.g. Game of Thrones. If a show by this title already exists, I want to prompt the user for confirmation that this is indeed a different show than the one in the db, and if no similar show exists I want to commit it to the db. How do I best handle this confirmation?
Some of my experiments are shown in the code below, but maybe I am going about this the wrong way. The base of my solution is to include a hidden field force, which should be set to 1 if the user gets prompted if he is sure he wants to commit this data, so that I can read out whether this thing is 1 to decide whether the user clicked submit again, thereby telling me that he wants to store it.
I would love to hear what you guy's think on how to solve this.
views.py
class TvshowModelForm(forms.ModelForm):
force = forms.CharField(required=False, initial=0)
def __init__(self, *args, **kwargs):
super(TvshowModelForm, self).__init__(*args, **kwargs)
class Meta:
model = Tvshow
exclude = ('user')
class UpdateTvshowView(UpdateView):
form_class = TvshowModelForm
model = Tvshow
template_name = "tvshow_form.html"
#Only the user who added it should be allowed to edit
def form_valid(self, form):
self.object = form.save(commit=False)
#Check for duplicates and similar results, raise an error/warning if one is found
dup_list = get_object_duplicates(Tvshow, title = self.object.title)
if dup_list:
messages.add_message(self.request, messages.WARNING,
'A tv show with this name already exists. Are you sure this is not the same one? Click submit again once you\'re sure this is new content'
)
# Experiment 1, I don't know why this doesn't work
# form.fields['force'] = forms.CharField(required=False, initial=1)
# Experiment 2, does not work: cleaned_data is not used to generate the new form
# if form.is_valid():
# form.cleaned_data['force'] = 1
# Experiment 3, does not work: querydict is immutable
# form.data['force'] = u'1'
if self.object.user != self.request.user:
messages.add_message(self.request, messages.ERROR, 'Only the user who added this content is allowed to edit it.')
if not messages.get_messages(self.request):
return super(UpdateTvshowView, self).form_valid(form)
else:
return super(UpdateTvshowView, self).form_invalid(form)
Solution
Having solved this with the help of the ideas posted here as answers, in particular those by Alexander Larikov and Chris Lawlor, I would like to post my final solution so others might benefit from it.
It turns out that it is possible to do this with CBV, and I rather like it. (Because I am a fan of keeping everything OOP) I have also made the forms as generic as possible.
First, I have made the following forms:
class BaseConfirmModelForm(BaseModelForm):
force = forms.BooleanField(required=False, initial=0)
def clean_force(self):
data = self.cleaned_data['force']
if data:
return data
else:
raise forms.ValidationError('Please confirm that this {} is unique.'.format(ContentType.objects.get_for_model(self.Meta.model)))
class TvshowModelForm(BaseModelForm):
class Meta(BaseModelForm.Meta):
model = Tvshow
exclude = ('user')
"""
To ask for user confirmation in case of duplicate title
"""
class ConfirmTvshowModelForm(TvshowModelForm, BaseConfirmModelForm):
pass
And now making suitable views. The key here was the discovery of get_form_class as opposed to using the form_class variable.
class EditTvshowView(FormView):
def dispatch(self, request, *args, **kwargs):
try:
dup_list = get_object_duplicates(self.model, title = request.POST['title'])
if dup_list:
self.duplicate = True
messages.add_message(request, messages.ERROR, 'Please confirm that this show is unique.')
else:
self.duplicate = False
except KeyError:
self.duplicate = False
return super(EditTvshowView, self).dispatch(request, *args, **kwargs)
def get_form_class(self):
return ConfirmTvshowModelForm if self.duplicate else TvshowModelForm
"""
Classes to create and update tvshow objects.
"""
class CreateTvshowView(CreateView, EditTvshowView):
pass
class UpdateTvshowView(EditTvshowView, UpdateObjectView):
model = Tvshow
I hope this will benefit others with similar problems.
I will post it as an answer. In your form's clean method you can validate user's data in the way you want. It might look like that:
def clean(self):
# check if 'force' checkbox is not set on the form
if not self.cleaned_data.get('force'):
dup_list = get_object_duplicates(Tvshow, title = self.object.title)
if dup_list:
raise forms.ValidationError("A tv show with this name already exists. "
"Are you sure this is not the same one? "
"Click submit again once you're sure this "
"is new content")
You could stick the POST data in the user's session, redirect to a confirmation page which contains a simple Confirm / Deny form, which POSTs to another view which processes the confirmation. If the update is confirmed, pull the POST data out of the session and process as normal. If update is cancelled, remove the data from the session and move on.
I have to do something similar and i could do it using Jquery Dialog (to show if form data would "duplicate" things) and Ajax (to post to a view that make the required verification and return if there was a problem or not). If data was possibly duplicated, a dialog was shown where the duplicated entries appeared and it has 2 buttons: Confirm or Cancel. If someone hits in "confirm" you can continue with the original submit (for example, using jquery to submit the form). If not, you just close the dialog and let everything as it was.
I hope it helps and that you understand my description.... If you need help doing this, tell me so i can copy you an example.
An alternative, and cleaner than using a vaidationerror, is to use Django's built in form Wizard functionality: https://django-formtools.readthedocs.io/en/latest/wizard.html
This lets you link multiple forms together and act on them once they are all validated.
First, I did look at this question, but its over a year old. Surely now there is a good way in Django 1.1.1 to carry filter selection forward after a user clicks the save button in the Admin.
In a table with thousands of records, filtering is essential. And if a user makes several filter choices that effort shouldn't have to be repeated.
The answer is still the same: out of the box, Django doesn't support this behavior. There are a couple of tickets in the issue tracker with patches: #3777, #6903. The middleware class in this comment works without modifying Django code.
This feature has been added to Django as part of the 1.6 release and is enabled now by default. It is described in the release notes:
ModelAdmin now preserves filters on the list view after creating,
editing or deleting an object. It’s possible to restore the previous
behavior of clearing filters by setting the preserve_filters attribute
to False.
another way is to use this snippet http://djangosnippets.org/snippets/2531/
Class Modeladmin_perso(admin.ModelAdmin):
def add_view(self, request, *args, **kwargs):
result = super(Modeladmin_perso, self).add_view(request, *args, **kwargs )
# Look at the referer for a query string '^.*\?.*$'
ref = request.META.get('HTTP_REFERER', '')
if ref.find('?') != -1:
# We've got a query string, set the session value
request.session['filtered'] = ref
if request.POST.has_key('_save'):
"""
We only kick into action if we've saved and if
there is a session key of 'filtered', then we
delete the key.
"""
try:
if request.session['filtered'] is not None:
result['Location'] = request.session['filtered']
request.session['filtered'] = None
except:
pass
return result
"""
Used to redirect users back to their filtered list of locations if there were any
"""
def change_view(self, request, object_id, extra_context={}):
"""
save the referer of the page to return to the filtered
change_list after saving the page
"""
result = super(Modeladmin_perso, self).change_view(request, object_id, extra_context )
# Look at the referer for a query string '^.*\?.*$'
ref = request.META.get('HTTP_REFERER', '')
if ref.find('?') != -1:
# We've got a query string, set the session value
request.session['filtered'] = ref
if request.POST.has_key('_save'):
"""
We only kick into action if we've saved and if
there is a session key of 'filtered', then we
delete the key.
"""
try:
if request.session['filtered'] is not None:
result['Location'] = request.session['filtered']
request.session['filtered'] = None
except:
pass
return result
the good thing is you don't have to hack anything.
This feature has been a request to the Django project for a long time (the ticket was opened 5 years ago).
Fortunately this annoying behavior was fixed in trunk. Expect it to be included in Django 1.6.
Here's what I did inside render_change_form to generate a back button with preserved_filters.
def generate_back_url(self, request):
opts = self.model._meta
post_url = reverse(
"admin:%s_%s_changelist" % (opts.app_label, opts.model_name),
current_app=self.admin_site.name,
)
preserved_filters = self.get_preserved_filters(request)
return add_preserved_filters(
{"preserved_filters": preserved_filters, "opts": opts}, post_url
)