Can I use a middleware to preserve several user selected choices in between page requests?
I have several values, namely vehicle year, make, model, series, style, color, and transmission. I want to have the user being able to select a choice while keeping the previously selected choice active. I do not want to use sessions for this as I want the URL to be bookmarkable.
I was thinking of something like:
def get_choices(self):
try:
return self.REQUEST["year"]
except (KeyError, ValueError, TypeError):
return 1
class ChoicesMiddleware(object):
def process_request(self, request):
.....
I'm also not sure how to return all the choices under get_choices().
EDIT 1
def get_choices(self):
user_choices = {}
for key in ["year", "model", "make", "series", "style"]:
user_choices[key] = self.REQUEST.get(key)
return user_choices
class ChoicesMiddleware(object):
def process_request(self, request):
return get_choices(self)
EDIT 2
My URLConf is as below:
(r'^inventory/(?P<year>\d{4})(?P<make>[-\w\s]+)
(?P<model>[-\w\s]+)(?P<series>[-\w\s]+)(?P<body>[-\w\s]+)
(?P<exterior>[-\w\s]+)(?P<interior>[-\w\s]+)
(?P<transmission>[-\w\s]+)$', 'inventory'),
Then the view is as below:
def inventory(request, page_by=None, year=None, make=None,
model=None, series=None, body=None, interior=None, exterior=None,
transmission=None):
#Initialize empty variable list.
kwargs = {}
if "year" in request.GET:
year = request.REQUEST["year"]
kwargs['common_vehicle__year__year__exact'] = year
....The rest of the vars are populated in the same way.
Do you want to automatically add the user choices as GET parameters to the URL?
I do not think you would be able to add GET request parameters to a URL via middleware.
You can store them in GET, no problem there. Return via dict. I didn't understand the part about preserving user's choice - you want to have several options for year, for example? Then you need arrays in GET, not values. But for values its simple:
def get_choices(self):
user_choices = {}
for key in ["year", "model", "maker"]:
user_choices[key] = self.REQUEST.get(key)
return user_choices
Related
I have a Model with a lot of entries, so I'm using django-filters to filter the model, I initially load an empty table and from there I use the filter to view the items.
Everything works fine, the page loads initially with no entry, after I filter, django shows the correct items.
The Url gets a parameter: /retetabloc/?acordcadru=532(532 is the filter) but when I try to update an entry, the filter resets(the parameter is still in the URL) and the whole db is loaded.
I don't quite understand how to pass the filter parameter to the RetetaBlocUpdate, so that after the update is done it returns to the filtered items like in the ListView.
views.py
class RetetaBlocListview(LoginRequiredMixin, CoreListView):
model = RetetaBloc
def get_queryset(self, *args, **kwargs):
pdb.set_trace()
acordcadru = self.request.GET.get("acordcadru")
queryset = RetetaBloc.objects.filter(acordcadru=acordcadru)
return queryset
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = RetetaBlocFilter(self.request.GET, queryset=self.get_queryset())
pdb.set_trace()
return context
class RetetaBlocUpdate(LoginRequiredMixin, AjaxUpdateView):
model = RetetaBloc
form_class = RetetaBlocForm
Thank you.
If you'd like filters to be remembered you could add them to a session variable instead. That way filters would be recalled even if they didn't go back directly from the update page (and you'd wouldn't have redundant URL querystring on pages where they weren't needed).
Something like:
def get_queryset(self, *args, **kwargs):
pdb.set_trace()
#check for new filter in URL first
acordcadru = self.request.GET.get("acordcadru")
#if nothing check for session variable
if not acordcadru:
acordcadru = self.request.session.get('acordcadru')
#if something in URL querystring, set it in session variable
else:
self.request.session['acordcadru'] = acordcadru
queryset = RetetaBloc.objects.filter(acordcadru=acordcadru)
return queryset
In my view I want to request data from database, and on other page I also need this information, but I don't want to write this request logic again.
views.py
def account(request):
user_id = request.user.id
balance = Customer.objects.filter(name_id=user_id).values("usd").first()
bonus = Customer.objects.filter(name_id=user_id).values("bonus").first()
context1 = {'usd':balance['usd'],
'bonus':bonus['bonus'],
return render(request, 'account.html', context1)
def main(request):
(here I also need to request balance and bonus)
In def main(request): and on other pages I also need balance and bonus, but I don't really want to request this values in every view.
Can I somehow write it somewhere once and to render on pages, and do not write it in every view??
First of all, I don't think that doing query of the same Customer model twice for balance and for bonus is more efficient than query Customer instance and access its attributes as customer.balance/customer.bonus.
Nevertheless, there are two main options to make this code reusable:
Put it in the function
def get_balance_and_bonus(request):
customer = Customer.objects.filter(name_id=request.user.id).first()
return customer.balance, customer.bonus
And use it in view as:
balance, bonus = get_balance_and_bonus(request)
Second (more sophisticated) method is to make a decorator for all views that require the customer.
Decorator to deduce customer:
def add_customer(func):
def wrap(*args, **kwargs):
request = args[0]
user_id = request.user.id
kwargs['customer'] = Customer.objects.filter(name_id=user_id).first()
return func(*args, **kwargs)
return wrap
Then add it to all functions that need customer (Note that view in this case must have the respective argument in it)
#add_customer
def account(request, customer):
context1 = {'usd': customer.balance,
'bonus': customer. bonus,
return render(request, 'account.html', context1)
#add_customer
def main(request, customer):
# here customer.balance and customer.bonus are available
I am creating an expense submission system, which is multi user. For the purpose of this question, there are two models: Claim and Journey. A user creates a claim and each claim can have multiple journeys.
In the Journey CreateView, the following code:
Autofills the claim dropdown for which a journey is being logged, based on the claim pk, passed in the URL.
Based on the logged in user, only shows claims for that user (in the dropdown)
Sets the HTML attribute's of the <input>; type=date renders the date selector, and min='2018-09-10' specifies a disallowed date range:
In the following view, I am calculating the min and max dates, which output correctly in the sanity check:
class JourneyCreateView(CreateView):
model = Journey
form_class = JourneyForm
def get_initial(self):
try:
# Calculate date limit for the date picker
min = Claim.objects.get(id=self.kwargs['claim']).week_commencing
max = min + timedelta(days=7)
# Obtain the claim ID from the URL
claim = self.kwargs['claim']
# Sanity check
print (claim, min, max)
return {'claim': claim, 'min':min, 'max':max}
except Exception as ex:
print (ex)
return {}
def get_form_kwargs(self, *args, **kwargs):
# Only show claims owned by the logged in user in the claim dropdown
kwargs = super().get_form_kwargs(*args, **kwargs)
kwargs['alloweduser'] = self.request.user.id
return kwargs
And the Form:
class JourneyForm(forms.ModelForm):
# set html attribs which will apply to the form.
date = forms.CharField(widget=forms.TextInput(attrs={'type':'date',
'min':'2018-09-10'
}))
class Meta:
model = Journey
fields = ['date', 'distance','claim']
def __init__(self,alloweduser,*args,**kwargs):
# Make sure only to display the user's own claims.
super (JourneyForm,self ).__init__(*args,**kwargs)
self.fields['claim'].queryset = Claim.objects.filter(tech_id=alloweduser)
In this code claim is also returned by get_initial() and correctly pre-populates the claim dropdown with the current claim:
return {'claim': claim, 'min':min, 'max':max}
However, where I am confused is how I access the min and max variables, returned by get_initial() in the third line of the form, to replace the manual test string.
I have solved this myself. My solution is:
In the CreateView, put the logic in get_from_kwargs instead of the get_initial overide :
d = Claim.objects.get(id=self.kwargs['claim']).week_commencing
kwargs['min'], kwargs['max'] = d, d+timedelta(days=7)
The min and max then become arguments when overiding __init__ in the ModelForm:
def __init__(self,alloweduser,min,max,*args,**kwargs):
# Make sure only to display the user's own claims.
super (JourneyForm,self ).__init__(*args,**kwargs)
self.fields['claim'].queryset = Claim.objects.filter(tech_id=alloweduser)
self.fields['date'] = forms.CharField(widget=forms.TextInput(attrs={'type':'date',
'min':min, 'max':max
}))
I want to filter multiple fields with multiple queries like this:
api/listings/?subburb=Subburb1, Subburb2&property_type=House,Apartment,Townhouse,Farm .. etc
Are there any built in ways, I looked at django-filters but it seems limited, and I think I would have to do this manually in my api view, but its getting messy, filtering on filters on filters
filtering on filters on filters is not messy it is called chained filters.
And chain filters are necessary because sometime there is going to be property_type some time not:
if property_type:
qs = qs.filter(property_type=property_type)
If you are thinking there is going to be multiple queries then not, it will still executed in one query because queryset are lazy.
Alternatively you can build a dict and pass it just one time:
d = {'property_type:': property_type, 'subburb': subburb}
qs = MyModel.objects.filter(**d)
Complex filters are not out of the box supported by DRF or even by django-filter plugin. For simple cases you can define your own get_queryset method
This is straight from the documentation
def get_queryset(self):
queryset = Purchase.objects.all()
username = self.request.query_params.get('username', None)
if username is not None:
queryset = queryset.filter(purchaser__username=username)
return queryset
However this can quickly become messy if you are supported multiple filters and even some of them complex.
The solution is to define a custom filterBackend class and a ViewSet Mixin. This mixins tells the viewset how to understand a typical filter backend and this backend can understand very complex filters all defined explicitly, including rules when those filters should be applied.
A sample filter backend is like this (I have defined three different filters on different query parameters in the increasing order of complexity:
class SomeFiltersBackend(FiltersBackendBase):
"""
Filter backend class to compliment GenericFilterMixin from utils/mixin.
"""
mapping = {'owner': 'filter_by_owner',
'catness': 'filter_by_catness',
'context': 'filter_by_context'}
def rule(self):
return resolve(self.request.path_info).url_name == 'pet-owners-list'
Straight forward filter on ORM lookups.
def filter_by_catness(self, value):
"""
A simple filter to display owners of pets with high catness, canines excuse.
"""
catness = self.request.query_params.get('catness')
return Q(owner__pet__catness__gt=catness)
def filter_by_owner(self, value):
if value == 'me':
return Q(owner=self.request.user.profile)
elif value.isdigit():
try:
profile = PetOwnerProfile.objects.get(user__id=value)
except PetOwnerProfile.DoesNotExist:
raise ValidationError('Owner does not exist')
return Q(owner=profile)
else:
raise ValidationError('Wrong filter applied with owner')
More complex filters :
def filter_by_context(self, value):
"""
value = {"context_type" : "context_id or context_ids separated by comma"}
"""
import json
try:
context = json.loads(value)
except json.JSONDecodeError as e:
raise ValidationError(e)
context_type, context_ids = context.items()
context_ids = [int(i) for i in context_ids]
if context_type == 'default':
ids = context_ids
else:
ids = Context.get_ids_by_unsupported_contexts(context_type, context_ids)
else:
raise ValidationError('Wrong context type found')
return Q(context_id__in=ids)
To understand fully how this works, you can read up my detailed blogpost : http://iank.it/pluggable-filters-for-django-rest-framework/
All the code is there in a Gist as well : https://gist.github.com/ankitml/fc8f4cf30ff40e19eae6
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
)