In the code below, the querysets are for filtering/sorting my products and they work. As you can see if I want to filter already filtered products I just add another queryset URL to it. The problem is how duplicated the code looks. Is there another way around?
Example of how the page looks and URL works
ProductsListView views.py
class ProductsListView(ListView):
model = Product
template_name = 'products_list.html'
context_object_name = 'products'
def get_queryset(self):
products = Product.objects.all()
# SORT BY PRICE
price = self.request.GET.get('price', None)
if price == 'desc':
products = products.order_by('-price')
if price == 'asc':
products = products.order_by('price')
# -- SORT BY PRICE END --
# SORT BY BRAND
brand = self.request.GET.get('brand', None)
if brand is not None:
products = products.filter(brand__iexact=brand)
# -- SORT BY BRAND END --
# SORT BY SEARCH
search = self.request.GET.get('search', None)
if search is not None:
products = products.filter(name__icontains=search)
# -- SORT BY SEARCH END --
# -- SORT BY CATEGORY
category_id = self.request.GET.get('category', None)
if category_id is not None:
products = products.filter(category__id=int(category_id))
# -- SORT BY CATEGORY END --
return products
def get_context_data(self, **kwargs):
context = super().get_context_data()
# CATEGORIES
categories = Category.objects.all()
context['categories'] = categories
# --
# CATEGORY NAME
category_id = self.request.GET.get('category', None)
try:
category_name = Category.objects.get(pk=category_id)
except ObjectDoesNotExist:
category_name = None
context['category_name'] = category_name
# --
brand_name = self.request.GET.get('brand')
context['brand_name'] = brand_name
price_sort_type = self.request.GET.get('price')
context['price_sort_type'] = price_sort_type
# CATEGORY PRODUCTS
# Filtering products by category if in a category
if category_name is not None:
category_products = Product.objects.filter(category__id=category_id)
else:
category_products = Product.objects.all()
context['category_products'] = category_products
# --
return context
ProductsList template
<ul class="list-group">
<li class="list-group-item mr-3">
<a class="dropdown-toggle text-dark" data-toggle="dropdown" href="#">
Brand
</a>
<ul class="dropdown-menu" role="menu" aria-labelledby="BrandDropDown">
{% for p in category_products %}
{% if category_name and price_sort_type %}
<li><a class="dropdown-item" href="{% url 'products' %}?category={{p.category.id}}&price={{price_sort_type}}&brand={{p.brand | urlencode}}">{{p.brand}}</a></li>
{% elif category_name %}
<li><a class="dropdown-item" href="{% url 'products' %}?category={{p.category.id}}&brand={{p.brand | urlencode}}">{{p.brand}}</a></li>
{% elif brand_name %}
<li><a class="dropdown-item" href="{% url 'products' %}?brand={{p.brand}}&price={{price_sort_type}}">{{p.brand}}</a></li>
{% else %}
<li><a class="dropdown-item" href="{% url 'products' %}?brand={{p.brand}}">{{p.brand}}</a></li>
{% endif %}
{% endfor %}
</ul>
</li>
</ul>
Related
I have two models, effectively a parent/child relationship. I have used inlineformset factory successfully for most of my objectives but one is proving rather troublesome. I only want to show the notes authored by the logged on user and enable that user to add, edit and delete note records authored by them. I am getting notes from all users logged on or not.
Django version 4.1, Python version 3.8.1
My Classes
class Fungi(models.Model):
CommonName = models.CharField(max_length=255, blank=False, null=False, default='Common Name')
LatinName = models.CharField(max_length=255, blank=False, null=False, default='Latin Name')
...
slug = models.SlugField(null=True)
...
class FungiNotes(models.Model):
Fungi = models.ForeignKey(Fungi, max_length=255, blank=False, null=False,
NoteAuthor = models.IntegerField(default=1, blank=True, null=True)on_delete=models.CASCADE, related_name='fungi_notes')
Note = models.CharField(max_length=255, blank=True, null=True, default='NoData')
...
slug = models.SlugField(null=True)
......
self.slug = slugify(self.Fungi_id)
Forms.py
FungiNotesFormset = inlineformset_factory(
Fungi,
FungiNotes,
extra=1,
labels='',
can_delete=True,
exclude=('Fungi','slug','NoteCount','NoteUser'),
)
views.py
class FunginotesEditView(SingleObjectMixin, FormView): #https://www.youtube.com/watch?v=OduVfuv44K8
model = FungiNotes
template_name = 'fungi_notes.html'
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=Fungi.objects.all())
return super().get(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
self.object = self.get_object(queryset=Fungi.objects.all())
return super().post(request, *args, **kwargs)
def get_form(self, form_class=None):
print('get_form__self.object +== ', self.object)
return FungiNotesFormset(**self.get_form_kwargs(), instance=self.object)
def form_valid(self, form):
form.save()
messages.add_message(
self.request,
messages.SUCCESS,
'Changes were saved.'
)
return HttpResponseRedirect(self.get_success_url())
def get_success_url(self):
return reverse('FungiDetail-Page', kwargs={'slug': self.object.slug})
urls.py
path('Fungis/<int:pk>/notes/edit/', views.FunginotesEditView.as_view(), name='Fungi_note_add_edit_delete'),
Fungi_notes.html
{% block DetailBlock %}
<form action="" method="post" enctype="multipart/form-data">
{% for hidden_field in form.hidden_fields %}
{{ hidden_field.errors }}
{{ hidden_field }}
{% endfor %}
{% csrf_token %}
{{ form.management_form }}
{{ form.non_form_errors }}
<div class="detail">
<h3><label class="sub-content">Update Fungi Notes</label></h3>
{% for fungi_note_form in form.forms %}
<h4>
{% if fungi_note_form.instance.id %}
Fungi Note:
{% else %}
{% if fungi_note_form.forms|length > 1 %}
Add another Note
{% else %}
Add a Note
{% endif %}
{% endif %}
</h4>
{% for hidden_field in fungi_note_form.hidden_fields %}
{{ hidden_field.errors }}
{% endfor %}
{{ refs.non_field_errors }}
<div class="sub-content">
<h4>
{{ fungi_note_form|crispy }}
</h4>
</div>
{% endfor %}
<p>
<button type="submit" class="btn btn-success">Update Note(s)</button>
Cancel
</p>
</div>
</form>
{% endblock DetailBlock %}
My approach has been to try and edit the formset and limit it to records as can be retrieved from the database by:
SELECT
Fungi.id,
FungiNotes.*
FROM
FungiNotes
INNER JOIN Fungi ON Fungi.id = FungiNotes.slug
WHERE
FungiNotes.NoteUser = 29 AND Fungi.id = 3
I have searched high and low for away to do this with no luck. I have a suspicion that 'get_form_kwargs()' might be in the solution somewhere!!!
Thank you
I am building a category and subcategory tree for my eCommerce website. Category model has a TreeForeignKey to itself so that categories can have many other subcategories as children. In my product.html route, I successfully rendered the categories tree with {& recursetree [models] &} tag (see first image below). When the user clicks on say the "Pumps" link, the user will be brought to another page that shows "Pumps" and its subcategories only. However, when i tried to replicate the same thing with {& recursetree [models] &}, it did not work out. I only see "Pumps" parent and none of its children subcategories. I've made sure that i passed in a treequeryset into the recursetree tag so that i dont run into "object not subscriptable error". However, i have hit a roadblock and do not know what is wrong with my code.
models.py
from django.db import models
from mptt.models import MPTTModel, TreeForeignKey, TreeManyToManyField
from ckeditor_uploader.fields import RichTextUploadingField
from django.urls import reverse
# Create your models here.
class Category(MPTTModel):
Status = (
('True', 'True'),
('False', 'False'),
)
bool_choices = (
(True, 'Yes'),
(False, 'No'),
)
parent = TreeForeignKey('self', blank = True, null = True, related_name = 'children', on_delete=models.CASCADE)
name = models.CharField(max_length=50)
slug = models.SlugField(unique=True, null=False)
description = models.TextField()
status = models.CharField(max_length=10, choices = Status)
is_active = models.BooleanField(choices=bool_choices)
meta_keywords = models.CharField(max_length=255)
meta_description = models.CharField(max_length=255)
created_at = models.DateTimeField(auto_now_add = True)
updated_at = models.DateTimeField(auto_now = True)
image = models.ImageField(blank = True, upload_to = 'images/')
class Meta:
db_table = 'categories'
verbose_name_plural = 'Categories'
def __str__(self):
return f"{self.name}"
class MPTTMeta:
order_insertion_by = ['name']
def get_absolute_url(self):
return reverse('category_detail', kwargs={'slug':self.slug})
views.py
def product(request):
category = Category.objects.all()
context = {'category': category}
return render(request, 'product.html', context)
def category(request, slug):
category = Category.objects.all().filter(slug=slug)
context = {'category': category}
return render(request, 'category.html', context)
urls.py
from django.urls import path
from . import views
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path("product/", views.product, name='product'),
path("<str:slug>/", views.category, name='category'),
]+ static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
product.html
{% extends 'layout.html' %}
{% block title %}
{% endblock %}
{% block body %}
<div>
{% load mptt_tags %}
<div class = "container">
<ul class="root">
{% recursetree category %}
<li>
{{ node.name }}
{% if not node.is_leaf_node %}
<ul class="children">
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>
</div>
</div>
{% endblock %}
[Categories and its subcategories successfully rendered in product.html]
[1]: https://i.stack.imgur.com/WO2ZH.png
category.html
{% extends 'layout.html' %}
{% block title %}
{{ category.id }}
{% endblock %}
{% block body %}
{% load mptt_tags %}
<div class = "container">
<ul class="root">
{% recursetree category %}
<li>
{{ node.name }}
{% if not node.is_leaf_node %}
<ul class="children">
{{ children }}
</ul>
{% endif %}
</li>
{% endrecursetree %}
</ul>
</div>
{% endblock %}
[Pumps' subcategories failed to render in the category.html]
[2]: https://i.stack.imgur.com/6DRkQ.png
The output from category = Category.objects.all().filter(slug=slug) is a <TreeQuerySet [<Category: Pumps>]> only.
If i need to render the children as well, i need to call the following line to grab the descendants and the model instance itself.
category = category.get_descendants(include_self=True)
I am working on a small Flask app and the user is able to update a list of items via using a form to submit some data.
I am currently using SQL Alchemy as the ORM for my database and using the list in my template to display the items. I want to update it so that when the list is updated. The page is updated without the user having to reload the page.
I have tried this with AJAX using the below script but the update is not occurring.
$(document).ready(function(){
$('form').on('submit',function (e) {
$.ajax({
type: 'post',
url: '/todo',
data: $('#todoInput').serialize(),
success: function (q) {
console.log(q);
}
});
e.preventDefault();
});
}
My template:
{% extends "base.html" %}
{% block script %}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="{{ url_for('static', filename='js/custom.js') }}"></script>
{% endblock %}
{% block content %}
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-success" role="alert">
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<div class="row justify-content-md-center">
<div class="col-md-auto">
<h1>What needs to be done today?</h1>
</div>
</div>
<div class="row justify-content-md-center">
<div class="col-md-auto">
<form action="" method="post">
{{ form.hidden_tag() }}
<div class="form-group">
{{ form.todo (id="todoInput") }}
{{ form.submit (class_="btn btn-primary btn-sm") }}
</div>
</form>
<div id="todoList">
<ul class="list-group">
{% for todo in todos %}
<li class="list-group-item d-flex justify-content-between align-items-center">
{{ todo.body }}
<button type="button" class="close" aria-label="Close"><span aria-hidden="true">×</span></button>
</li>
{% endfor %}
</ul>
</div>
</div>
</div>
{% endblock %}
Model:
from datetime import datetime
from app import db, login
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash
class User(UserMixin, db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
email = db.Column(db.String(120), index=True, unique=True)
password_hash = db.Column(db.String(128))
todos = db.relationship('Todo', backref='owner', lazy='dynamic')
def __repr__(self):
return '<User {}>'.format(self.username)
def set_password(self, password):
self.password_hash = generate_password_hash(password)
def check_password(self, password):
return check_password_hash(self.password_hash, password)
#login.user_loader
def load_user(id):
return User.query.get(int(id))
class Todo(db.Model):
id = db.Column(db.Integer, primary_key=True)
body = db.Column(db.String(140))
timestamp = db.Column(db.DateTime, index=True, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
def __repr__(self):
return '<Todo {}>'.format(self.body)
Route:
#app.route('/todo', methods=['GET', 'POST'])
def todo():
if current_user.is_authenticated:
form = TodoForm()
todos = current_user.todos.order_by(Todo.timestamp.desc()).all()
if form.validate_on_submit():
new_todo = Todo(body=form.todo.data, owner=current_user)
db.session.add(new_todo)
db.session.commit()
flash('Todo added')
todos = current_user.todos.order_by(Todo.timestamp.desc()).all()
return render_template('todo.html', form=form, todos=todos)
return render_template('todo.html', form=form, todos=todos)
flash('You first need to login or register')
return redirect(url_for('index'))
The todos list is what im trying to update. But it just reloads the page :/
You need to do two things. Firstly you need to be sure that your database is updated. That means that your form was submitted successfully. This can be done by submitting the form manually via an ajax call. This is more or less what you did. Did you check, whether your backend receives the form-submission and updates the database?
Secondly you need to update the html in the browser. Normally the page is refreshed automatically if your form is submitted. If you dont want a page reload, you need to add some client-side logic, to ensure that the page is updated. This can easily be done with a little bit of javascript.
Edit:
Your custom js might be missing }); at the end. Or did you just forget to post it here?
Hi I have a 'member' model that references a 'role' model. What I would like is when a user creates a 'member' they can either choose from a list of default 'roles' (that have been loaded to the database when the server starts using fixtures) or they can click on add role and then type in and click submit and the newly created role is created and assigned to the newly created 'member' object.
I would like it so that I am able to just create a role without submitting, and the role then appears in the dropdown menu for the role field in the member model. Is this where I would need to learn about AJAX to implement this feature?
Models
class MemberRole(models.Model,get_fields):
name = models.CharField(max_length = 20)
def __unicode__(self):
return self.name
class Member(models.Model,get_fields):
first_name = models.CharField(max_length = 20)
role = models.ForeignKey(MemberRole, null = True, blank = True)
View
def add_member(request):
model_url = 'member-add'
if request.method == "POST":
rform = MemberRoleForm(request.POST, instance=MemberRole())
mform = MemberForm(request.POST, instance=Member())
if rform.is_valid() and mform.is_valid():
new_role = rform.save()
new_member = mform.save(commit=False)
new_member.role = new_role
new_member.save()
return HttpResponseRedirect('members')
else:
rform = MemberRoleForm(instance=MemberRole())
mform = MemberForm(instance=Member())
return render_to_response('create_model.html', {'role_form': rform, 'member_form': mform, 'model_url': model_url,},context_instance=RequestContext(request))
snippet create_model.html
<div id = "subtemplate">
<form action="{% url model_url %}" method="POST">
{% csrf_token %}
{% if model_url == 'member-add' %}
{% for field in member_form %}
{% if field.label == 'Role' %}
<div id="roleExistsMemberForm">
<button type="button" onclick="showDiv()">Add Role</button>
{{ field.errors }}
{{ field.label_tag }} {{ field }} {{ field.help_text }}
</div>
{% else %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endif %}
{% endfor %}
<div Id="addRoleOnMemberForm">
{% for field in role_form %}
{{ field.errors }}
{{ field.label_tag }} {{ field }}
{% endfor %}
</div>
{% else %}
<ul>
<li>{{ form.as_ul }}</li>
</ul>
{% endif %}
<div id="centerbuttons">
<input id="save_contact" type="submit" value="Add"/>
</div>
</form>
Yes, Ajax would be the first point of call for something like this. You can submit the form in the background to a Django view, the view could then respond with a fresh list of options to re-populate the drop down list with.
The following is my "view" which renders a form through "editpost.html". I want to update the timestamp (datetime field) to be edited by the system time, but don't want that to be visible to the user. I tried using "hidden" keyword in the modelform definition Meta class, but that does not update the time correctly. I tried doing it in the template but doesn't work. Any ideas?
def editpost(request, postid):
blog = BlogPost.objects.get(pk=postid)
if request.method == 'POST':
form = BlogPostForm(request.POST, instance=blog)
blog = form.save()
return HttpResponseRedirect('/blog/%s' % str(postid))
else:
form = BlogPostForm(initial={'timestamp': datetime.now()}, instance=blog)
form.save(commit=False)
return render_to_response("editpost.html", {'form': form, 'postid': postid}, RequestContext(request))
editpost.html
{% block content %}
<form action="." method="post">{% csrf_token %}
{% for field in form %}
{% if field.label_tag != "Timestamp" %}
<div class="fieldWrapper">
{{ field.errors }}
{{ field.label_tag }} {{ field }}
</div>
{% endif %}
{% endfor %}
<input type="submit" value="Save" />
</form>
{% endblock %}
May be better would be rewrite def save(...) of the model. You can try it:
def save(self, *args, **kwargs):
self.timestamp = datetime.now()
super(Dog,self).save(*args, **kwargs)