sorting of store goods - python

I'm using ListView in my Class Based Views and I want to sort the goods by the selected field by the user, but I couldn't find the information I needed
view
class Shop(ListView):
template_name = 'essense/shop.html'
context_object_name = 'items'
paginate_by = 9
allow_empty = False
def get_queryset(self):
return Item.objects.all()
def get_context_data(self, *, object_list=None, **kwargs):
***context***
return context
def get_ordering(self):
ordering = self.request.GET.get('orderby',)
print(ordering)
return ordering
template
<form action="{% url 'shop' %}" method="get" id="sortProduct">
<div class="product-sorting d-flex">
<p>Sort by:</p>
<select type="submit" name="select">
<option type="submit" name="orderby" value="price">Price: $$ - $</option>
<option type="submit" name="orderby" value="-price">Price: $ - $$</option>
</select>
<input type="submit" class="d-none" value="">
</div>
</form>

You should not override the get_queryset, since there is where the ordering logic is happening. You can specify the model with the model attribute [Django-doc]:
class Shop(ListView):
model = Item
template_name = 'essense/shop.html'
context_object_name = 'items'
paginate_by = 9
allow_empty = False
# no get_queryset
def get_ordering(self):
return self.request.GET.get('orderby',)
or if you want to filter the queryset by making a super call:
class Shop(ListView):
model = Item
template_name = 'essense/shop.html'
context_object_name = 'items'
paginate_by = 9
allow_empty = False
def get_queryset(self, *args, **kwargs):
return super().get_queryset(*args, **kwargs).all()
def get_ordering(self):
return self.request.GET.get('orderby',)
Note that it is not very safe to let a user select an arbitrary order by. Hashed passwords can be retrieved if one for example orders on user__password for example. Usually it is better to retain a list of fields that are allowed to order by.

Related

How to autoincrement values checkbox with jinja2 (Django) with reset

I need to autoincrement value in my checkbox and reset value when I generated new massive of checkbox
forloop.count dont reset
{% for ans in Answ %}
{% if ans.question_id_id == Questions.id %}
<input type="hidden" value="{{ Questions.id }}" name="id">
<div class="form-check" ><label><input type="checkbox" value="{{ ans.id }}" name="answer"> {{ ans.answer }} </label></div>
{% endif %}
{% endfor %}
views.py
class AnswerQuestionView (LoginRequiredMixin, DetailView):
login_url = '/login'
redirect_field_name = 'redirect_to'
model = Question
template_name = 'index.html'
context_object_name = 'Questions'
slug_field = 'pk'
def get_context_data(self, **kwargs):
context = super(AnswerQuestionView, self).get_context_data(**kwargs)
context['user_group'] = self.request.user.groups.values_list()[0][1]
context['Answ'] = QuestAnswer.objects.all()
return context
This is one of the many reasons why you should not do filtering in the template. Another very important one is performance: as the number of answers will grow, eventually the template rendering will take a lot of time.
You can filter in the view with:
class AnswerQuestionView(LoginRequiredMixin, DetailView):
login_url = '/login'
redirect_field_name = 'redirect_to'
model = Question
template_name = 'index.html'
context_object_name = 'question'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_group'] = self.request.user.groups.values_list()[0][1]
context['answers'] = QuestAnswer.objects.filter(question_id=self.object)
return context
You probably can even use the related name:
class AnswerQuestionView(LoginRequiredMixin, DetailView):
login_url = '/login'
redirect_field_name = 'redirect_to'
model = Question
template_name = 'index.html'
context_object_name = 'question'
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['user_group'] = self.request.user.groups.values_list()[0][1]
context['answers'] = self.object.questionanswer_set.all()
return context
Note: Normally one does not add a suffix …_id to a ForeignKey field, since Django
will automatically add a "twin" field with an …_id suffix. Therefore it should
be question, instead of question_id.

How to filter and paginate in ListView Django

I have a problem when I want to paginate the filter that I create with django_filter, in my template it shows me the query set and filter but paginate does not work, I would like to know why this happens and if you could help me.
I'll insert snippets of my code so you can see.
This is my views.py
PD: i have all the necesary imports.
#method_decorator(staff_member_required, name='dispatch')
class EmployeeListView(ListView):
model = Employee
paginate_by = 4
def dispatch(self, request, *args, **kwargs):
if not request.user.has_perm('employee.view_employee'):
return redirect(reverse_lazy('home'))
return super(EmployeeListView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = EmployeeFilter(self.request.GET, queryset = self.get_queryset())
return context
filters.py
import django_filters
from .models import Employee, Accident
class EmployeeFilter(django_filters.FilterSet):
class Meta:
model = Employee
fields = {
'rutEmployee' : ['startswith']
}
You should override get_queryset.This means you have to put your filter in get_queryset like this:
#method_decorator(staff_member_required, name='dispatch')
class EmployeeListView(ListView):
model = Employee
paginate_by = 4
def dispatch(self, request, *args, **kwargs):
if not request.user.has_perm('employee.view_employee'):
return redirect(reverse_lazy('home'))
return super(EmployeeListView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['filter'] = EmployeeFilter(self.request.GET, queryset = self.get_queryset())
return context
def get_queryset(self):
queryset = super().get_queryset()
return EmployeeFilter(self.request.GET, queryset=queryset).qs
and use object_list instead of filter in employee_list.html like this:
{% for employee in object_list|dictsort:"id" reversed %}
You could also try this:
(A snippet from my source code)
class ModelListView(ListView):
model = YourModel
paginate_by = 4 # Change this if you don't intend to paginate by 4
ordering = model_field_to_order_by
# variable used to know if a match was found for the search made using django_filters
no_search_result = False
def get_queryset(self, **kwargs):
search_results = YourDjangoFiltersForm(self.request.GET, self.queryset)
self.no_search_result = True if not search_results.qs else False
# Returns the default queryset if an empty queryset is returned by the django_filters
# You could as well return just the search result's queryset if you want to
return search_results.qs.distinct() or self.model.objects.all()
def get_query_string(self):
query_string = self.request.META.get("QUERY_STRING", "")
# Get all queries excluding pages from the request's meta
validated_query_string = "&".join([x for x in re.findall(
r"(\w*=\w{1,})", query_string) if not "page=" in x])
# Avoid passing the query path to template if no search result is found using the previous query
return "&" + validated_query_string.lower() if (validated_query_string and not self.no_search_result) else ""
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
# Pass to template if you want to do something whenever an empty queryset is return by django_filters
context["no_search_result"] = self.no_search_result
# This is the query string which should be appended to the current page in your template for pagination, very critical
context["query_string"] = self.get_query_string()
context['filter'] = YourDjangoFiltersForm()
return context
In your html template you need to append the querystring passed to your template from the view, a sample is shown below
{% for i in page_obj.paginator.page_range %}
{% if page_obj.number == i %}
<li class="page-item active" aria-current="page">
<span class="page-link">{{ i }}<span class="sr-only">(current)</span></span>
</li>
{% elif i > page_obj.number|add:'-5' and i < page_obj.number|add:'5' %} <li class="page-item"><a
class="page-link" href="?page={{ i }}{{ query_string }}">{{ i }}</a></li>
{% endif %}
{% endfor %}

How set initial values to fields on form?

enter code hereI'm having some problems to solve a problem. I have a template, which allows the user to change some of their account settings. My goal is to initialize the form, with the user's default values, and he can keep or change them (by after submit form). However, until now the page does not render these values. I'm using a class based view, CreateView, for this purpose.
My code is listed below.
Here, is my CreateView.
class DetailUserInfoView(LoginRequiredMixin ,CreateView):
model = CustomUser.CustomUser
template_name = 'users/InfoUser.html'
login_url = settings.LOGOUT_REDIRECT_URL
context_object_name = 'user'
form_class = CustomUserChangeForm
def get_object(self):
self.model = self.request.user
return self.model
def get_initial(self):
initial = super(DetailUserInfoView, self).get_initial()
initial = initial.copy()
initial[config.USERNAME] = self.request.user.username
initial[config.FIRST_NAME] = self.request.user.first_name
initial[config.LAST_NAME] = self.request.user.last_name
return initial
def get_form_kwargs(self):
kwargs = {'initial': self.get_initial()}
return kwargs
def get_context_data(self, **kwargs): #GET OBJECT ACTS AFTER THAN GET_OBJECT --> EXAMPLE OF GET_CONTEXT_DATA, I DIDN'T NEED THIS
context = super(DetailUserInfoView, self).get_context_data(**kwargs)
context['username'] = self.request.user.username
return context
Here the form.
class CustomUserChangeForm(UserChangeForm):
class Meta(UserChangeForm.Meta):
model = CustomUser.CustomUser
fields = ('email', 'password', 'first_name', 'last_name', 'username', 'userType')
And finally an extract of template.
<div id="infoMayOverride">
<form class="getOverridedValues" method="post">
{% csrf_token %}
<div id="usernameData">
<label>{{ form.username.label_tag }}</label> <!--MODEL ON CREATEUSERVIEW IS CUSTOMUSER, AND NOW I NEED TO USE THIS FIELDS AND INHERITED FIELDS FROM USER CLASS-->
<input type="text" id="usernameInput" value="{{ form.username }}">
</div>
<div id="firstNameData">
<label>{{ form.first_name.label_tag }}</label>
<input type="text" id="firstNameInput" value="{{ form.first_name }}">
</div>
<div id="lastNameData">
<label>{{ form.last_name.label_tag }}</label>
<input type="text" id="lastNameInput" value="{{ form.last_name }}">
</div>
<div id="divBtnChangeProfile">
<input type="submit" class="btnChangeProfile" value="Atualizar Profile">
</div>
</form>
</div>
I'd appreciate it if you could help me. I am new to the Django environment, and have tried many approaches, and I have not yet been able to solve this problem.
--------------------------- Update ------------------------------------
Now, i can get initial values. But to view them i need to write on input form: form.username.initial, and with this i can't after submit form to update user values.
Anyone knows how to solve this type of problem??
I finally got this problem solved. I will make my solution available, since it can help other people.
I had to make some changes to the code I provided behind.
Below is the code of view.
class DetailUserInfoView(LoginRequiredMixin, UpdateView):
model = CustomUser.CustomUser
template_name = 'users/InfoUser.html'
login_url = settings.LOGOUT_REDIRECT_URL
context_object_name = 'user'
form_class = CustomUserChangeForm
def get_object(self, queryset=None):
return self.request.user
def get_form_kwargs(self):
kwargs = super(DetailUserInfoView, self).get_form_kwargs()
u = self.request.user
kwargs['username_initial'] = u.username
kwargs['fName_initial'] = u.first_name
kwargs['lName_initial'] = u.last_name
return kwargs
def get_context_data(self, **kwargs): #GET OBJECT ACTS AFTER THAN GET_OBJECT --> EXAMPLE OF GET_CONTEXT_DATA, I DIDN'T NEED THIS
context = super(DetailUserInfoView, self).get_context_data(**kwargs)
form_class = self.get_form_class()
form = self.get_form(form_class)
context['form'] = form
return context
My form (with init function, to set initial values on form, and is called by def get_form_kwargs(self)).
class CustomUserChangeForm(UserChangeForm):
def __init__(self, *args, **kwargs):
username_initial = kwargs.pop('username_initial', None)
fName_initial = kwargs.pop('fName_initial', None)
lName_initial = kwargs.pop('lName_initial', None)
super(CustomUserChangeForm, self).__init__(*args, **kwargs)
self.fields['username'].initial = username_initial
self.fields['first_name'].initial = fName_initial
self.fields['last_name'].initial = lName_initial
class Meta(UserChangeForm.Meta):
model = CustomUser.CustomUser
fields = ('username', 'email', 'first_name', 'last_name')
And finnaly, in template I replace the tag input with {{ form.username }}.
I hope it can help someone who has the same problem.

Django get_queryset filtering with multiple select

I doing a form of filtering by the foreignkey field with two search conditions: OR and AND and multiple choices.
forms.py
class EmployeeSkillFilter(forms.ModelForm):
skills = forms.ModelMultipleChoiceField(queryset=Technology.objects.all(), )
class Meta:
model = Employee
fields = ['skills']
templates.py
<form method="get" enctype="multipart/form-data">
{% csrf_token %}
<input type="checkbox" name="and" value="AND"> Choice and<br>
<input type="checkbox" name="or" value="OR"> Choice OR<br>
{{ skill_filter }}
<button type="submit" class="btn btn-info" type="button">Go!</button>
</form>
views.py
class AccountList(AuthorizedMixin, ListView):
model = Employee
template_name = 'employee_list.html'
def get_queryset(self, *args, **kwargs):
condition_and = self.request.GET.get('and', None)
condition_or = self.request.GET.get('or', None)
skill = self.request.GET.get('skills', None)
if condition_and and skill:
object_list = self.model.objects.filter(skills__name_id=skill) ??????
if condition_or and tech:
object_list = self.model.objects.filter(
Q(skills__name_id=skill) |
Q(skills__name_id=skill)
) ?????
else:
object_list = self.model.objects.all()
My urls looks like
localhost://....or=OR&skills=5&skills=4&skills=3
The problem is that when I select multiple objects in a form, I don't know how to pass them all to the condition
skills__name_id=skill
Use the __in lookup [Django-doc] lookup, and in case of and logic, count the number of matches:
from django.db.models import Count
class AccountList(AuthorizedMixin, ListView):
model = Employee
template_name = 'employee_list.html'
def get_queryset(self, *args, **kwargs):
condition_and = self.request.GET.get('and', None)
condition_or = self.request.GET.get('and', None)
skills = self.request.GET.getlist('skills')
if condition_and:
object_list = self.model.objects.filter(
skills__in=skills
).annotate(
nskills=Count('skills')
).filter(
nskills=len(skills)
)
elif condition_or:
object_list = self.model.objects.filter(
skills__in=skills
).distinct()
else:
object_list = self.model.objects.all()

Pre-populating a child models django create form with a parent's ID

I have followed the guidelines from This answer in order to pass Parent pk to the child creation page. At the moment though it is not working and I am seeing the following log.
[14/Jul/2017 13:15:37] "POST /catalog/productstatus/2/create/ HTTP/1.1" 200 4001
I'm not sure what I'm doing wrong, here is the code I currently have.
Models
Models.py
class Product(models.Model):
serial_number = models.CharField(unique=True, max_length=15)
class ProductStatus(models.Model):
serial_number = models.ForeignKey('Product', on_delete=models.CASCADE, null=True)
status = models.CharField(max_length=20, blank=True, default='Stock', help_text='Products status')
date = models.DateTimeField(auto_now_add=True)
View
class ProductStatusCreate(CreateView):
model = ProductStatus
template_name = 'catalog/productstatus_create.html'
form_class = ProductStatusModelForm
def form_valid(self, form):
productstatus = form.save(commit=False)
product_id = form.data['product_id']
product = get_object_or_404(Product, id=product_id)
productstatus.product = product
return super(ProductStatusCreate, self).form_valid(form)
def get_context_data(self, **kwargs):
context = super(ProductStatusCreate, self).get_context_data(**kwargs)
context['s_id'] = self.kwargs['product_id']
return context
def get_success_url(self):
if 'product_id' in self.kwargs:
product = self.kwargs['product_id']
else:
product = self.object.product.pk
return reverse_lazy('product_detail', kwargs={'pk': product})
Forms
class ProductStatusModelForm(forms.ModelForm):
class Meta:
model = ProductStatus
fields = ['status',]
def __init__(self, *args, **kwargs):
self.fields["product"] = forms.CharField(widget=forms.HiddenInput())
super(ProductStatusModelForm, self).__init__( *args, **kwargs)
templates/myapp/product_detail.html
New
urls.py
urlpatterns += [
url(r'^productstatus/(?P<product_id>\d+)/create/$', views.ProductStatusCreate.as_view(), name='productstatus_create'),
]
productstatus_create.html
{% extends "base_generic.html" %}
{% block content %}
<h2>New Product Status</h2>
</br>
<form action="" method="post">
{% csrf_token %}
<table>
<input type=hidden id="id_product" name="product" value="{{ s_id }}">
{{ form }}
</table>
<input type="submit" value="Submit" />
</form>
</br>
{% endblock %}
When looking at the page's source the value does get populated but when I submit the form nothing happens.
Why do you have views.ProductInstanceCreate.as_view() in your urls.py but the view you show is called ProductStatusCreate? Are you sure you are using the right view?
You are creating a 'product' hidden field in your form, but not providing a value for it anywhere. Your template output then has two product fields, and the latter (blank) is taken, so returns an error saying it is required.
None of this outputting the product ID to the template in order to read it back in is necessary - you always have the ID available to you in the URL kwargs.
You can get rid of your get_context_data, and the extra field code in the Form and template. Your form_valid can be something like:
def form_valid(self, form):
product = get_object_or_404(Product, id=self.kwargs['product_id'])
form.instance.product = product
return super().form_valid(form)
And product_id will always be in self.kwargs, so your get_success_url can be shorter too:
def get_success_url(self):
product = self.kwargs['product_id']
return reverse('product_detail', kwargs={'pk': product})

Categories