I've implemented a table search product with Ajax and it works well.
But now, I want to build dynamically my table taking in account the number of my warehouses can be increase.
search.js
data.forEach((item) => {
const newName = (item.nom).slice(0, 30) + "...";
tableBody.innerHTML += `
<tr>
<th>${item.sku}</th>
<td>${item.etat__etat}</td>
<td class="small">${newName}</td>
<td>${item.famille__nom}</td>
<td>${item.mageid}</td>
<td>${item.adresse}</td>
models.py (model for witch I need a set)
class SstStock(models.Model):
warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE)
product = models.ManyToManyField(Produit)
qty = models.IntegerField()
last_update = models.DateTimeField(default=timezone.now)
views.py
def search_product2(request):
if request.method == 'POST':
search_str = json.loads(request.body).get('searchText')
products = Produit.objects.filter(sku__icontains=search_str) | Produit.objects.filter(
nom__icontains=search_str) | Produit.objects.filter(mageid__icontains=search_str)
data = products.values(
'id',
'sku',
'nom',
[...]
'sststock',
[...]
'cau_cli',
'maxsst2',
)
return JsonResponse(list(data), safe=False)
Directly in template I could do :
template
{% for produit in produits %}
{{ produit.sku}}<br>
{% for sst in produit.sststock_set.all %}
<span>{{sst.warehouse.code}} - {{ sst.qty }}</span><br>
{% endfor %}
<br>
{% endfor %}
But I couldn't find the way to pass the the sststock_set.all() in the JsonResponse. I got well a "sststock" value in it but it contains only the last value of the set instead of an array/dict of the whole set.
console.log()
qty: 7
sku: "ACP863"
sststock: 68095
68095 is the last ID of my set.
Worse, when I try to get item.sststock in the ForEach product, in my JS, it returns Undefined.
Any idea please ?
Found the way to apply #WillemVanOnsem advice with serializer.
Before all, my first error war to apply ManyToMany instead of ForeignKey on:
product = models.ManyToManyField(Produit)
After, I have set a serializer that retrieves the different stocks (warehouse_id + qty) and adds it to the Product model (with "source" parameter):
serializers.py
from rest_framework import serializers
from .models import Produit, SstStock
class StockSearchSerializer(serializers.ModelSerializer):
class Meta:
model = SstStock
fields = '__all__'
fields = ['warehouse_id', 'qty']
class ProductSearchSerializer(serializers.ModelSerializer):
sststock = StockSearchSerializer(source='sststock_set', many=True)
class Meta:
model = Produit
fields = '__all__'
To finish, I use the serializer with "many=True" in the view and return its result that will be handled by JS on my search page:
views.py
def search_product(request):
if request.method == 'POST':
search_str = json.loads(request.body).get('searchText')
products = Produit.objects.prefetch_related(
Prefetch('sststock_set',
SstStock.objects.select_related('warehouse'))
).filter(sku__icontains=search_str) | Produit.objects.filter(
nom__icontains=search_str) |
Produit.objects.filter(mageid__icontains=search_str)
serializer = ProductSearchSerializer(products, many=True)
data = serializer.data
return JsonResponse(list(data), safe=False)
And as wished, stocks array is added in the json response
Related
I have seen this approach in many web applications (e.g. when you subscribe for an insurance), but I can't find a good way to implement it in django. I have several classes in my model which inherit from a base class, and so they have several fields in common. In the create-view I want to use that inheritance, so first ask for the common fields and then ask for the specific fields, depending on the choices of the user.
Naive example, suppose I want to fill a database of places
class Place(Model):
name = models.CharField(max_length=40)
address = models.CharField(max_length=100)
class Restaurant(Place):
cuisine = models.CharField(max_length=40)
website = models.CharField(max_length=40)
class SportField(Place):
sport = models.CharField(max_length=40)
Now I would like to have a create view when there are the common fields (name and address) and then the possibility to choose the type of place (Restaurant / SportField). Once the kind of place is selected (or the user press a "Continue" button) new fields appear (I guess to make it simple the page need to reload) and the old one are still visible, already filled.
I have seen this approach many times, so I am surprised there is no standard way, or some extensions already helping with that (I have looked at Form Wizard from django-formtools, but not really linked to inheritance), also doing more complicated stuff, as having more depth in inheritance.
models.py
class Place(models.Model):
name = models.CharField(max_length=40)
address = models.CharField(max_length=100)
class Restaurant(Place):
cuisine = models.CharField(max_length=40)
website = models.CharField(max_length=40)
class SportField(Place):
sport = models.CharField(max_length=40)
forms.py
from django.db import models
from django import forms
class CustomForm(forms.Form):
CHOICES = (('restaurant', 'Restaurant'), ('sport', 'Sport'),)
name = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Name'}))
address = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Address'}))
type = forms.ChoiceField(
choices=CHOICES,
widget=forms.Select(attrs={'onChange':'renderForm();'}))
cuisine = forms.CharField(required=False, widget=forms.TextInput(attrs={'placeholder': 'Cuisine'}))
website = forms.CharField(required=False, widget=forms.TextInput(attrs={'placeholder': 'Website'}))
sport = forms.CharField(required=False, widget=forms.TextInput(attrs={'placeholder': 'Sport'}))
views.py
from django.http.response import HttpResponse
from .models import Restaurant, SportField
from .forms import CustomForm
from django.shortcuts import render
from django.views import View
class CustomView(View):
def get(self, request,):
form = CustomForm()
return render(request, 'home.html', {'form':form})
def post(self, request,):
data = request.POST
name = data['name']
address = data['address']
type = data['type']
if(type == 'restaurant'):
website = data['website']
cuisine = data['cuisine']
Restaurant.objects.create(
name=name, address=address, website=website, cuisine=cuisine
)
else:
sport = data['sport']
SportField.objects.create(name=name, address=address, sport=sport)
return HttpResponse("Success")
templates/home.html
<html>
<head>
<script type="text/javascript">
function renderForm() {
var type =
document.getElementById("{{form.type.auto_id}}").value;
if (type == 'restaurant') {
document.getElementById("{{form.website.auto_id}}").style.display = 'block';
document.getElementById("{{form.cuisine.auto_id}}").style.display = 'block';
document.getElementById("{{form.sport.auto_id}}").style.display = 'none';
} else {
document.getElementById("{{form.website.auto_id}}").style.display = 'none';
document.getElementById("{{form.cuisine.auto_id}}").style.display = 'none';
document.getElementById("{{form.sport.auto_id}}").style.display = 'block';
}
}
</script>
</head>
<body onload="renderForm()">
<form method="post" action="/">
{% csrf_token %}
{{form.name}}<br>
{{form.address}}<br>
{{form.type}}<br>
{{form.website}}
{{form.cuisine}}
{{form.sport}}
<input type="submit">
</form>
</body>
</html>
Add templates folder in settings.py
TEMPLATES = [
{
...
'DIRS': [os.path.join(BASE_DIR, 'templates')],
...
]
I've created a 2-page working example using modified Class Based Views.
When the form is submitted on the first page, an object of place_type is created. The user is then redirected to the second page where they can update existing details and add additional information.
No separate ModelForms are needed because the CreateView and UpdateView automatically generate the forms from the relevant object's model class.
A single template named place_form.html is required. It should render the {{ form }} tag.
# models.py
from django.db import models
from django.urls import reverse
class Place(models.Model):
"""
Each tuple in TYPE_CHOICES contains a child class name
as the first element.
"""
TYPE_CHOICES = (
('Restaurant', 'Restaurant'),
('SportField', 'Sport Field'),
)
name = models.CharField(max_length=40)
address = models.CharField(max_length=100)
place_type = models.CharField(max_length=40, blank=True, choices=TYPE_CHOICES)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('place_update', args=[self.pk])
# Child models go here...
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('create/', views.PlaceCreateView.as_view(), name='place_create'),
path('<pk>/', views.PlaceUpdateView.as_view(), name='place_update'),
]
# views.py
from django.http import HttpResponseRedirect
from django.forms.models import construct_instance, modelform_factory
from django.views.generic.edit import CreateView, UpdateView
from django.urls import reverse_lazy
from . import models
class PlaceCreateView(CreateView):
model = models.Place
fields = '__all__'
def form_valid(self, form):
"""
If a `place_type` is selected, it is used to create an
instance of that Model and return the url.
"""
place_type = form.cleaned_data['place_type']
if place_type:
klass = getattr(models, place_type)
instance = klass()
obj = construct_instance(form, instance)
obj.save()
return HttpResponseRedirect(obj.get_absolute_url())
return super().form_valid(form)
class PlaceUpdateView(UpdateView):
fields = '__all__'
success_url = reverse_lazy('place_create')
template_name = 'place_form.html'
def get_object(self, queryset=None):
"""
If the place has a `place_type`, get that object instead.
"""
pk = self.kwargs.get(self.pk_url_kwarg)
if pk is not None:
obj = models.Place.objects.get(pk=pk)
if obj.place_type:
klass = getattr(models, obj.place_type)
obj = klass.objects.get(pk=pk)
else:
raise AttributeError(
"PlaceUpdateView must be called with an object pk in the URLconf."
)
return obj
def get_form_class(self):
"""
Remove the `place_type` field.
"""
model = self.object.__class__
return modelform_factory(model, exclude=['place_type',])
We did something similar manually, we created the views and forms based on design and did the linkage based on if conditions.
I think a nice solution would be to dynamically access subclasses of the main class and then do the necessary filtering/lists building.
UPD: I've spent some more time today on this question and made a "less raw" solution that allows to use the inheritance.
You can also check the code below deployed here. It has only one level of inheritance (as in example), though, the approach is generic enough to have multiple levels
views.py
def inheritance_view(request):
all_forms = {form.Meta.model: form for form in forms.PlaceForm.__subclasses__()}
all_forms[models.Place] = forms.PlaceForm
places = {cls._meta.verbose_name: cls for cls in models.Place.__subclasses__()}
# initiate forms with the first one
context = {
'forms': [forms.PlaceForm(request.POST)],
}
# check sub-forms selected on the forms and include their sub-forms (if any)
for f in context['forms']:
f.sub_selected = request.POST.get('{}_sub_selected'.format(f.Meta.model._meta.model_name))
if f.sub_selected:
sub_form = all_forms.get(places.get(f.sub_selected))
if sub_form not in context['forms']:
context['forms'].append(sub_form(request.POST))
# update some fields on forms to render them on the template
for f in context['forms']:
f.model_name = f.Meta.model._meta.model_name
f.sub_forms = {x.Meta.model._meta.verbose_name: x for x in f.__class__.__subclasses__()}
f.sub_options = f.sub_forms.keys() # this is for rendering selector on the form for the follow-up forms
page = loader.get_template(template)
response = HttpResponse(page.render(context, request))
return response
forms.py
class PlaceForm(forms.ModelForm):
class Meta:
model = models.Place
fields = ('name', 'address',)
class RestaurantForm(PlaceForm):
class Meta:
model = models.Restaurant
fields = ('cuisine', 'website',)
class SportFieldForm(PlaceForm):
class Meta:
model = models.SportField
fields = ('sport',)
templates/inheritance.html
<body>
{% for form in forms %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
{% if form.sub_options %}
<select class="change-place" name="{{ form.model_name }}_sub_selected">
{% for option in form.sub_options %}
<option value="{{ option }}" {% if option == form.sub_selected %}selected{% endif %}>{{ option }}</option>
{% endfor %}
</select>
{% endif %}
<button type="submit">Next</button>
</form>
{% endfor %}
</body>
What I didn't make here is saving the form to the database. But it should be rather trivial using the similar snippet:
for f in context['forms']:
if f.is_valid():
f.save()
Add a PlaceType table, and a FK, e.g. type_of_place, to the Place table:
class PlaceType(Model):
types = models.CharField(max_length=40) # sportsfield, restaurants, bodega, etc.
class Place(Model):
name = models.CharField(max_length=40)
address = models.CharField(max_length=100)
type_of_place = models.ForeignKey('PlaceType', on_delete=models.SET_NULL, null=True)
class Restaurant(Place):
cuisine = models.CharField(max_length=40)
website = models.CharField(max_length=40)
This allows you to create a new Place as either SportsField, restaurant or some other type which you can easily add in the future.
When a new place is created, you'll use the standard CreateView and Model Form. Then, you can display a second form which also uses a standard CreateView that is based on the type_of_place value. These forms can be on the same page (and with javascript on the browser side, you'll hide the second form until the first one is saved) or on separate pages--which may be more practical if you intend to have lots of extra columns. The two key points are as follows:
type_of_place determines which form, view, and model to use. For
example, if user chooses a "Sports Field" for type_of_place, then
you know to route the user off to the SportsField model form;
CreateViews are designed for creating just one object/model. When
used as intended, they are simple and easy to maintain.
There are lot of way you can handle multiple froms in django. The easiest way to use inlineformset_factory.
in your froms.py:
forms .models import your model
class ParentFrom(froms.From):
# add fields from your parent model
Restaurant = inlineformset_factory(your parent model name,Your Child model name,fields=('cuisine',# add fields from your child model),extra=1,can_delete=False,)
SportField = inlineformset_factory(your parent model name,Your Child model name,fields=('sport',# add fields from your child model),extra=1,can_delete=False,)
in your views.py
if ParentFrom.is_valid():
ParentFrom = ParentFrom.save(commit=False)
Restaurant = Restaurant(request.POST, request.FILES,) #if you want to add images or files then use request.FILES.
SportField = SportField(request.POST)
if Restaurant.is_valid() and SportField.is_valid():
ParentFrom.save()
Restaurant.save()
SportField.save()
return HttpResponseRedirect(#your redirect url)
#html
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
#{{ Restaurant.errors}} #if you want to show error
{{ Restaurant}}
{{ SportField}}
{{form}}
</form>
you can use simple JavaScript in your html for hide and show your any froms fields
I have a simple m2m relationship as below:
class Category(ModelBase):
name = models.CharField(max_length=255)
icon = models.CharField(max_length=50)
class Course(ModelBase):
name = models.CharField(max_length=255, unique=True)
categories = models.ManyToManyField(Category, related_name="courses")
I am using ListView to show all the courses in a category or all courses if no category provided.
views.py
class CourseListView(ListView):
model = Course
paginate_by = 15
template_name = "courses.html"
context_object_name = "courses"
def get_queryset(self):
queryset = (
super()
.get_queryset()
.select_related("tutor")
.prefetch_related("categories")
.filter(active=True)
)
category_id = self.kwargs.get("category_id")
return (
queryset
if not category_id
else queryset.filter(categories__in=[category_id])
)
def get_context_data(self, *args, **kwargs: Any) -> Dict[str, Any]:
context = super().get_context_data(**kwargs)
category_id = self.kwargs.get("category_id")
if category_id:
context["current_category"] = Category.objects.get(id=category_id)
context["categories"] = Category.objects.all()
return context
Django is making duplicate calls as I am doing something like this in the template.
<div class="icon"><span class="{{ course.categories.first.icon }}"></span></div>
Not sure why, help much appreciated. Thanks!
When you do .prefetch_related('categories') the result of this prefetch will be used when you access course.categories.all. Any other queryset on course.categories will do a fresh query. Since course.categories.first is a new queryset, it does not use the prefetched result.
What you want to access in your template is the first result from course.categories.all(). But this is not easy in the template. I would recommend a method on the Course model:
class Course(...):
...
def first_category(self):
# Equivalent to self.categories.first(), but uses the prefetched categories
categories = self.categories.all()
if len(categories):
return categories[0]
else:
return None
And then in your template you can call this method
<div class="icon"><span class="{{ course.first_category.icon }}"></span></div>
You can also access the first value like:
{{ course.categories.all.0.icon }}
It is not necessary to write a method.
because categories is ManyToMany which means one category may appear in many courses, but in the template you just calling the first category's icon, so there maybe more than two course with the same first category, and it will retrieve them all, i recommend using another for loop to loops through categories too.
{% for course in courses %}
<div>
<h1>{{ course.name</h1>
......
<h4>categories</h4>
{% for category in course.categories %}
<div class="icon"><span class="{{ category.icon }}"></span></div>
{% endfor %}
</div>
{% endfor %}
i have a 3 models namely category, brand, products. i have to include the category and brand in the products form for displaying it in drop down and selected value should be assigned in the products table.can anyone help me with it?
As you said, you have a three models. Category, Brand, Products.
models.py
class Category(models.Model):
#
# Your logic here
#
class Brand(models.Model):
#
# Your logic here
#
class Products(models.Model):
#
# Your logic here
#
Now generate forms based on models (using ModelForm) or just use Form class.
forms.py
class CategoryModelForm(forms.ModelForm):
class Meta:
model = Category
fields = '__all__' # for example
class BrandForm(forms.Form):
class Meta:
model = Brand
fields = '__all__' # for example
views.py
def index(request):
if request.method == 'POST':
cf = CategoryForm(data=request.POST)
bf = BrandForm(data=request.POST)
if cf.is_valid() and bf.is_valid(): # validate both form together
# cf.cleaned_data and bf.cleaned_data returns validated data.
print('CategoryForm valid data => ', cf.cleaned_data)
print('BrandForm valid data => ', bf.cleaned_data)
Products.objects.create(field1=cf.cleaned_data['some_field'], field2=bf.cleaned_data['some_field']) # you should implement your own logic. Performs database insert query.
return JsonResponse({'status_message': 'Successfully updated Products table'})
else:
cf = CategoryForm()
bf = BrandForm()
return render(request, 'index.html', {'cf': cf, 'bf': bf})
index.html
<form accept="" method="POST">
{% csrf_token %}
{{ cf.as_p }} <br />
{{ bf.as_p }}
<input type="submit" value="click" />
</form>
I hope it will helps you !
I am trying to show the field(s) related to its foreignkey on html. Let's see I have two models as shown below:
models.py
from django.db import models
class Model_Item(models.Model):
item_name = models.CharField(max_length = 100, null = False, blank = False, unique = True)
item_unit = models.CharField(max_length = 20, null = False, blank = False) # can be kilogram, pound, ounce, etc
def __unicode__(self):
return self.item_name
class Model_Weight(models.Model):
item = models.ForeignKey(Model_Item, to_field = "item_name")
item_weight = models.FloatField(null = True, blank = True)
def __unicode__(self):
return self.item
On Model_Item model, each item can have its own unit, and there can be many items. Then we will choose the item on the second model (Model_Weight), and insert the value of the weight that is according to its unit.
How can we show the corresponding "item_unit" in html, such that when we have selected the "item_name", its unit will show/hover somewhere in the webpage which enables us to put the correct weight value?
These are the rest of the codes:
forms.py
from django import forms
from .models import Model_Weight
class Form_Weight(forms.ModelForm):
class Meta:
model = Model_Weight
fields = ["item", "item_weight"]
views.py
from .models import Model_Weight
from .forms import Form_Weight
from django.views.generic import CreateView
class View_Weight_CV(CreateView):
form_class = Form_Weight
def form_valid(self, form):
instance = form.save(commit = False)
instance.user = self.request.user
return super(View_Weight_CV, self).form_valid(form)
html
<form method = "POST" action = "" enctype = "multipart/form-data"> {% csrf_token %}
{{ form.item}}
<!-- {{ form.model_item.item_unit }} Automatically shows this field once an item has been selected -->
{{ form.item_weight}}
<input type = "submit" value = "Submit">
</form>
The quick solution is to change __unicode__ method definition of Model_Item model
def __unicode__(self):
# add item_unit with name
return self.item_name + " (" + self. item_unit + ")"
Now in your HTML template, the item dropdown will be shown like
{{ form.item }} #--> Bread (Kg)
#--> Rice (Kg)
#--> ...
If you want to show unit under item dropdown, keep above settings as it is and add below javascript code at bottom of your HTML template
$(document).on('change', '#id_item', function(){
// you can also make ajax request from here
// I am using selected item text for now
var item = $(this).find("option:selected").text();
// item = 'Bread (Kg)'
var result = item.match(/\((.*)\)/);
// matched text inside round brackets
// result[1] = Kg
$( "#id_item" ).after( "<p>"+result[1]+"</p>" );
});
In my exercise I have a Django model of a book, having a field "genre". This field has the following option choices
GENRES_CHOICE = (
('ADV','Adventure'),
('FAN','Fantasy'),
('POE','Poetry'),
)
and the model field is
genre = models.CharField(max_length = 3, blank = False, choices = GENRES_CHOICE, db_index = True, editable = False)
In my template I would like to show to the user the list of the genres (Adventure, Fantasy, Poetry) and hava available the keys, in order to possibly use them as parameters.
In order to do so, I would like to have a function that returns the data structure GENRES_CHOICE, but I am not able to. How to solve this problem?
EDIT:
more code details
appname= mybookshelf, file -> models/Book.py
# possible choices for the gerne field
GENRES_CHOICE = (
('ADV','Adventure'),
('FAN','Fantasy'),
('POE','Poetry'),
)
class Book(models.Model):
"""
This is the book model
...
## ATTRIBUTES (better use init, but in Django not always possible)
id = models.CharField(max_length = 64, blank = False, unique = True, primary_key = True, editable = False)
""" unique id for the element """
genre = models.CharField(max_length = 3, blank = False, choices = GENRES_CHOICE, db_index = True, editable = False)
""" book genre """
published_date = models.DateField(null = True, auto_now_add = True, editable = False)
""" date of publishing """
Then, into another file, lets say MyFunctions.py I have
from mybookshelf.models import GENRES_CHOICE
def getBookCategories():
"""
This function returns the possible book categories
categories = GENRES_CHOICE
return categories
views.py
from app_name.models import GENRES_CHOICE
def view_name(request):
...............
return render(request, 'page.html', {
'genres': GENRES_CHOICE
})
page.html
{% for genre in genres %}
{{genre.1}}<br/>
{% endfor %}
I am not 100% sure this is what you are after, but if you want to show the user the list of GENRES_CHOICE you can do this in your templete:
{% for choice_id, choice_label in genres %}
<p> {{ choice_id }} - {{ choice_label }} </p>
{% endfor %}
ofcourse pass GENRES_CHOICE as genres
You could use the get_modelfield_display() method in your template, e.g.:
{{ book.get_genre_display }}