I designed a database for my django ecommerce project but it have some problems, the goal of
the this design is to have products with different specifications for example a mobile cell has it's own properties and a television too,
it is my models.py:
'''
from django.db import models
from mptt.models import MPTTModel, TreeForeignKey
from django.shortcuts import reverse
from model_utils import FieldTracker
from . import uploaders
class Category(MPTTModel):
name = models.CharField(max_length=50, unique=True)
parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True,
related_name='children')
slug = models.SlugField(max_length=75, unique=True)
tracker = FieldTracker(fields=['name'])
class MPTTMeta:
order_insertion_by = ['name']
def __str__(self):
category_names = [self.name]
node = self
while node.parent:
node = node.parent
category_names.append(node.name)
return ' / '.join(category_names[::-1])
def get_absolute_url(self):
return reverse('product_by_category', args=(self.slug,))
class ProductType(models.Model):
name = models.CharField(max_length=50, unique=True)
def __str__(self):
return self.name
class ProductSpecifications(models.Model):
name = models.CharField(max_length=50)
product_type = models.ForeignKey(ProductType, on_delete=models.CASCADE,
related_name='specifications')
class Meta:
unique_together = ('name', 'product_type')
def __str__(self):
return self.name
class Product(models.Model):
name = models.CharField(max_length=100, unique=True)
product_type = models.ForeignKey(ProductType, on_delete=models.CASCADE,
related_name='products')
category = models.ForeignKey(Category, on_delete=models.CASCADE,
related_name='products')
price = models.PositiveBigIntegerField()
discount_price = models.PositiveBigIntegerField(null=True, blank=True)
description = models.TextField(null=True, blank=True)
image = models.ImageField(upload_to=uploaders.product_img_uploader)
slug = models.SlugField(max_length=150, unique=True)
tracker = FieldTracker(fields=['slug', 'name', 'product_type'])
def __str__(self):
return self.name
def set_discount(self, percentage):
self.discount_price = self.price * (1 - percentage)
self.save()
#property
def is_discounted(self):
return bool(self.discount_price)
def remove_discount(self):
self.discount_price = None
self.save()
class ProductSpecificationValue(models.Model):
specification = models.ForeignKey(ProductSpecifications, on_delete=models.CASCADE)
product = models.ForeignKey(Product, on_delete=models.CASCADE,
related_name='specifications')
value = models.CharField(max_length=75, null=True, blank=True)
def __str__(self):
return ''
class Meta:
unique_together = ('specification', 'product')
'''
And admin.py:
'''
from django.contrib import admin
from django.http import HttpResponseRedirect
from mptt.admin import MPTTModelAdmin
from .models import *
from .forms import ProductSpecForm
#admin.register(Category)
class CategoryAdmin(MPTTModelAdmin):
readonly_fields = ('slug',)
class SpecificationInline(admin.TabularInline):
model = ProductSpecifications
extra = 2
#admin.register(ProductType)
class ProductTypeAdmin(admin.ModelAdmin):
inlines = (SpecificationInline,)
class SpecificationValueInline(admin.TabularInline):
model = ProductSpecificationValue
# form = ProductSpecForm
# fields = ('specification', 'value')
# readonly_fields = ('specification',)
#
# def has_add_permission(self, request, obj):
# return False
#
# def has_delete_permission(self, request, obj=None):
# return False
#admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
inlines = (SpecificationValueInline,)
readonly_fields = ('slug',)
# def response_post_save_add(self, request, obj):
# return HttpResponseRedirect(
# reverse("admin:%s_%s_change" % (self.model._meta.app_label,
# self.model._meta.model_name), args=(obj.id,)))
'''
the problem is in product admin panel when you want to add or change a product, I want the select box for specification in SpecificationValueInline form show me only specifications related to the product type not all specifications in db, the lines that I commented in admin.py with some signals and a form was my approach to solve this issue i dont know if it was the best help me please!
signals.py:
'''
from django.dispatch import receiver
from django.db.models.signals import pre_save, post_save
from .models import Category, Product, ProductSpecificationValue, ProductSpecifications
#receiver(pre_save, sender=Product)
#receiver(pre_save, sender=Category)
def initialize_slug(sender, instance, *args, **kwargs):
if (not instance.slug) or (instance.tracker.has_changed('name')):
instance.slug = instance.name.replace(' ', '_')
#receiver(post_save, sender=Product)
def initialize_specifications(sender, instance, created, **kwargs):
if created:
product_type = instance.product_type
for specification in product_type.specifications.all():
ProductSpecificationValue.objects.create(product=instance,
specification=specification)
elif instance.tracker.has_changed('product_type'):
ProductSpecificationValue.objects.filter(product=instance).delete()
product_type = instance.product_type
for specification in product_type.specifications.all():
ProductSpecificationValue.objects.create(product=instance,
specification=specification)
#receiver(post_save, sender=ProductSpecifications)
def add_new_specs_to_related_products(sender, instance, created, **kwargs):
if created:
product_type = instance.product_type
for product in product_type.products.all():
ProductSpecificationValue.objects.create(specification=instance,
product=product)
'''
forms.py:
'''
from django import forms
from django.forms import ModelChoiceField
from .models import ProductSpecificationValue, Product
class ProductSpecForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if hasattr(self.instance, 'product'):
self.fields['specification'] = ModelChoiceField(
queryset=self.instance.product.product_type.specifications.all())
class Meta:
model = ProductSpecificationValue
fields = ('specification', 'value')
'''
you can use formfield_for_foreignkey in SpecificationValueInline
class SpecificationValueInline(admin.TabularInline):
model = ProductSpecificationValue
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "specification":
product_id = request.resolver_match.kwargs.get('object_id')
productType = Product.objects.get(id = product_id).product_type
kwargs["queryset"] = ProductSpecification.objects.filter(product_type=productType)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
mohsen ma answer was usefull I made some changes and it got better but I still doubt it it is enough or best practice, if user changes the product type he/she should stay on change page to fill the specification idk how:
'''
#receiver(post_save, sender=Product)
def sync_specs_with_type(sender, instance, created, **kwargs):
if created or instance.tracker.has_changed('product_type'):
if not created:
instance.specifications.all().delete()
for spec in instance.product_type.specifications.all():
ProductSpecificationValue.objects.create(product=instance, specification=spec)
class SpecificationValueInline(admin.TabularInline):
model = ProductSpecificationValue
extra = 0
def formfield_for_foreignkey(self, db_field, request, **kwargs):
product_id = request.resolver_match.kwargs.get('object_id')
if product_id and db_field.name == "specification":
product_type = Product.objects.get(id=product_id).product_type
kwargs["queryset"] = ProductSpecifications.objects.filter(product_type=product_type)
return super().formfield_for_foreignkey(db_field, request, **kwargs)
#admin.register(Product)
class ProductAdmin(admin.ModelAdmin):
readonly_fields = ('slug',)
inlines = (SpecificationValueInline,)
def response_post_save_add(self, request, obj):
messages.add_message(request, messages.INFO, 'set you product specifications')
return HttpResponseRedirect(
reverse("admin:%s_%s_change" % (self.model._meta.app_label, self.model._meta.model_name), args=(obj.id,)))
def get_inlines(self, request, obj):
if obj:
return super().get_inlines(request, obj)
return ()
'''
Related
My models.py
from django.db import models
from django.contrib.auth.models import User
import datetime
from django.utils import timezone
# Create your models here.
class LiveClass(models.Model):
standard = models.IntegerField()
no_of_students_registered = models.IntegerField(default=0)
class Meta:
verbose_name_plural = 'Class'
def __str__(self):
return str(self.standard) + ' class'
class User_details(models.Model):
name = models.OneToOneField(User, on_delete = models.CASCADE, max_length=30)
standard = models.ForeignKey(LiveClass, on_delete=models.CASCADE)
email = models.EmailField(max_length=30)
mobile_number = models.IntegerField()
class Meta:
verbose_name_plural = 'User_details'
def __str__(self):
return self.name
class Mentor(models.Model):
name = models.CharField(max_length=30)
details = models.TextField()
ratings = models.FloatField(default=2.5)
class Meta:
verbose_name_plural = 'Mentors'
def __str__(self):
return self.name
class LiveClass_details(models.Model):
standard = models.ForeignKey(LiveClass, on_delete=models.CASCADE)
chapter_name = models.CharField(max_length=30)
chapter_details = models.TextField()
mentor_name = models.ForeignKey(Mentor, max_length=30, on_delete=models.CASCADE)
class_time = models.DateTimeField()
end_time = models.DateTimeField()
isDoubtClass = models.BooleanField(default=False)
doubtsAddressed = models.IntegerField(default=0)
class Meta:
verbose_name_plural = 'LiveClass_details'
def __str__(self):
return self.chapter_name
class SavedClass(models.Model):
class_details = models.ForeignKey(LiveClass_details, on_delete=models.CASCADE)
name = models.ForeignKey(User_details, on_delete=models.CASCADE)
is_registered = models.BooleanField(default=False)
is_attended = models.BooleanField(default=False)
class Meta:
verbose_name_plural = 'SavedClasses'
def __str__(self):
return 'SavedClass : ' + str(self.class_details)
my serializers.py
from rest_framework import serializers
from . import models
class LiveClass_serializer(serializers.ModelSerializer):
class Meta:
model = models.LiveClass
fields = '__all__'
class User_details_serializer(serializers.ModelSerializer):
class Meta:
model = models.User_details
fields = '__all__'
class LiveClass_details_serializer(serializers.ModelSerializer):
class Meta:
model = models.LiveClass_details
fields = '__all__'
class Mentor_serializer(serializers.ModelSerializer):
class Meta:
model = models.Mentor
fields = '__all__'
class SavedClass_serializer(serializers.ModelSerializer):
class Meta:
model = models.SavedClass
fields = '__all__'
my views.py
from django.shortcuts import render
from rest_framework import mixins
from rest_framework import generics
from django.contrib.auth.mixins import LoginRequiredMixin
from rest_framework import status
from django.contrib.auth.models import User
from rest_framework.response import Response
from . import serializers
from . import models
# Create your views here.
class ListLiveClass(mixins.ListModelMixin, LoginRequiredMixin, generics.GenericAPIView):
queryset = models.LiveClass_details.objects.all()
serializer_class = serializers.LiveClass_details_serializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
class LiveClassView(mixins.ListModelMixin,
mixins.CreateModelMixin,
LoginRequiredMixin,
generics.GenericAPIView):
queryset = models.LiveClass_details.objects.all()
serializer_class = serializers.LiveClass_details_serializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
if request.user.is_superuser:
return self.create(request, *args, **kwargs)
else:
return Response(status=status.HTTP_403_FORBIDDEN)
class LiveClassViewId(mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
mixins.DestroyModelMixin,
LoginRequiredMixin,
generics.GenericAPIView):
queryset = models.LiveClass_details.objects.all()
serializer_class = serializers.LiveClass_details_serializer
lookup_field = 'id'
def get(self, request, id=None, format=None):
if id:
return self.retrieve(request)
else:
return Response(status=status.HTTP_400_BAD_REQUEST)
def put(self, request, id, format=None):
if request.user.is_superuser:
return self.update(request, id)
else:
return Response(status=status.HTTP_403_FORBIDDEN)
def delete(self, request, id, format=None):
if request.user.is_superuser:
return self.destroy(request, id)
else:
return Response(status=status.HTTP_403_FORBIDDEN)
class ListMentors(mixins.ListModelMixin, LoginRequiredMixin, generics.GenericAPIView):
queryset = models.Mentor.objects.all()
serializer_class = serializers.Mentor_serializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
class ListUserDetails(mixins.ListModelMixin, LoginRequiredMixin, generics.GenericAPIView):
queryset = models.User_details.objects.all()
serializer_class = serializers.User_details_serializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
#api endpoints to save and register live classes
class SavedClassView(LoginRequiredMixin, mixins.ListModelMixin, mixins.DestroyModelMixin, generics.GenericAPIView):
model = models.SavedClass
serializer = serializers.SavedClass_serializer
def get_object(self):
return self.request.user.id
def get(self, request):
CurrentUserID = self.get_object()
In SavedClassView i want to get saved classes from the current logged user only and i want to preserve this user from getting other user saved classes , i have done something like above but i am getting no perfect logic that fits the working of django rest framework , please help me in achieving the above result
Add filter to queryset
queryset = models.User_details.objects.filter(user=request.user)
Just make sure to implement tokens so you can access request.user
how in forms.py class, overwrite the default value of the model category, without adding it to the html form?
forms.py
class FastForm(forms.ModelForm):
class Meta:
model = Orders
fields = ['device']
models.py
class Orders(models.Model):
device = models.CharField(max_length=150)
category = models.ForeignKey('Category', default=1, on_delete=models.PROTECT)
class Category(models.Model):
category = models.CharField(max_length=150, db_index=True)
UPD
views
class OrderAddView(TemplateView):
template_name = 'orders/order_add.html'
def get(self, request, *args, **kwargs):
context = super().get_context_data(**kwargs)
formOne = FastForm
context.update({'formOne': formOne})
return self.render_to_response(context)
def post(self, request, *args, **kwargs):
formOne = FastForm(self.request.POST)
if formOne.is_valid():
form_update = formOne.save(commit=False)
form_update.save()
return HttpResponseRedirect('orders_home')
else:
print('NotValid')
I am trying to populate my database with .csv files with django-import-export module but I keep running in this error
Line number: 1 - maximum recursion depth exceeded while calling a Python object 43, Sicilian, L, 0, 1,7, 45.7
whenever I try to populate my Pizza model through the admin UI.
Here is my CSV
admin.py
from django.contrib.admin import ModelAdmin
from import_export.admin import ImportExportModelAdmin
from .models import Topping, Pizza, Sub, Pasta, Salad, Dinner
class PizzaAdmin(ImportExportModelAdmin):
def save_related(self, request, form, formsets, change):
super(PizzaAdmin, self).save_related(request, form, formsets, change)
form.instance.toppings.add(Topping.objects.get(name='Cheese'))
#admin.register(Sub)
class SubAdmin(ImportExportModelAdmin):
pass
# Register your models here.
admin.site.register(Topping)
admin.site.register(Pizza, PizzaAdmin)
# admin.site.register(Sub)
admin.site.register(Pasta)
admin.site.register(Salad)
admin.site.register(Dinner)
models.py
from django.contrib import admin
from django.db import models
from django.contrib.auth.models import User
from django.dispatch import receiver
from django.db.models.signals import post_save
from django.core.validators import MinValueValidator, MaxValueValidator
from django.contrib.auth import get_user_model
# Create your models here.
class Topping(models.Model):
name = models.CharField(max_length=64)
def __str__(self):
return(f"{self.name}")
class Pizza(models.Model):
PIZZA_SIZES = (
('S', 'Small'),
('L', 'Large'),
)
pizza_type = models.CharField(max_length=64)
pizza_size = models.CharField(max_length=1, choices=PIZZA_SIZES)
qty_toppings = models.IntegerField(validators=[MinValueValidator(0), MaxValueValidator(3)], default=0)
toppings = models.ManyToManyField(Topping, related_name='pizzas', blank=True)
price = models.FloatField(help_text="Price in $")
def __str__(self):
return f"Size: {self.get_pizza_size_display()}, Type: {self.pizza_type}, Number of Toppings: {self.qty_toppings}, Price: {self.price}, Toppings: {self.toppings.in_bulk()}"
def save(self, *args, **kwargs):
# if 'toppings' not in kwargs:
# kwargs.setdefault('force_insert', True)
# kwargs.setdefault('force_update', True)
super(Pizza, self).save(*args, **kwargs)
self.toppings.add(Topping.objects.get(name='Cheese'))
# kwargs.setdefault('toppings', Topping.objects.get(name='Cheese'))
class Sub(models.Model):
SUBS_SIZES = (
('S', 'Small'),
('L', 'Large'),
)
subs_size = models.CharField(max_length=1, choices=SUBS_SIZES)
name = models.CharField(max_length=64)
price = models.IntegerField(help_text="Price in $")
def __str__(self):
return f"{self.name}, {self.get_subs_size_display()} : {self.price}"
class Pasta(models.Model):
name = models.CharField(max_length=64)
price = models.IntegerField(help_text="Price in $")
def __str__(self):
return f"{self.name} : {self.price}"
class Salad(models.Model):
name = models.CharField(max_length=64)
price = models.IntegerField(help_text="Price in $")
def __str__(self):
return f"{self.name} : {self.price}"
class Dinner(models.Model):
DINNER_SIZES = (
('S', 'Small'),
('L', 'Large'),
)
dinner_size = models.CharField(max_length=1, choices=DINNER_SIZES)
name = models.CharField(max_length=64)
price = models.IntegerField(help_text="Price in $")
def __str__(self):
return f"{self.name}, {self.get_dinner_size_display()} : {self.price}"
class Euser(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
def __str__(self):
return(f"{self.id}")
class ShoppingCart(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, null=True)
pizzas = models.ManyToManyField(Pizza)
subs = models.ManyToManyField(Sub)
pastas = models.ManyToManyField(Pasta)
dinners = models.ManyToManyField(Dinner)
number_of_articles = models.IntegerField(null=True)
price = models.DecimalField(max_digits=5, decimal_places=2, null=True)
def __str__(self):
return(f"{self.user}'s cart")
#receiver(post_save, sender=get_user_model())
def create_user_cart(sender, instance, created, **kwargs):
if created:
ShoppingCart.objects.create(user=instance)
# #receiver(post_save, sender=User)
# def create_user_cart(sender, instance, created, **kwargs):
# if created:
# ShoppingCart.objects.create(user=instance)
# #receiver(post_save, sender=User)
# def save_user_cart(sender, instance, **kwargs):
# instance.shoppingcart.save()
FYI: Used pandas to convert this same .csv to a .json, and I got KeyError encountered while trying to read file: pizza_sicilian.json
Any help will be greatly appreciated
i have faced the same issue
i would rather suggest you
in admin .py
#admin.register(Sub)
class SubAdmin(ImportExportModelAdmin):
list_display=["pizza","other objects in model"]
pass
list_display is a default function an the items should be in a list ...
this solved my issue , i think it will help many viewers!
Thnak you!
I am working on a set of product / category relationships in a Django application.
A product can belong to any category and needs to be ordered within that category, I am trying to do this using a Many to Many relationship with a "through=" option.
When a POST request is made via Ajax it takes the form of b'ordered_products=4&ordered_products=5', I received an error straight after the forms __init__ call that "5 is not one of the available choices" where 5 is the valid id of anOrderedCategoryManagedProduct object.
models.py
class Category(models.Model):
name = models.CharField(max_length=128)
slug = models.SlugField(max_length=128, unique=True)
class Product(models.Model):
name = models.CharField(max_length=128)
category_management = models.ManyToManyField(
Category,
related_name="category_managed_products",
through="OrderedCategoryManagedProduct",
blank=True,
verbose_name="Category Management",
)
class OrderedCategoryManagedProduct(SortableModel):
category = models.ForeignKey(
Category, on_delete=models.CASCADE, related_name="cm_products"
)
product = models.ForeignKey(
Product, on_delete=models.CASCADE, related_name="cm_categories"
)
class Meta:
ordering = ["sort_order"]
def get_ordering_queryset(self):
return self.product.category_management()
class SortableModel(models.Model):
sort_order = models.IntegerField(db_index=True, null=True)
class Meta:
abstract = True
views.py
# POST = <QueryDict: {'ordered_products': [5, 4]}>
#staff_member_required
#permission_required("menu.manage_menus")
def ajax_reorder_menu_items(request, category_pk):
category = get_object_or_404(Category, pk=category_pk)
form = ReorderCategoryProductsForm(request.POST, instance=category)
status = 200
ctx = {}
if form.is_valid():
form.save()
elif form.errors:
status = 400
ctx = {"error": form.errors}
return JsonResponse(ctx, status=status)
forms.py
class ReorderCategoryProductsForm(forms.ModelForm):
ordered_products = OrderedModelMultipleChoiceField(
queryset=OrderedCategoryManagedProduct.objects.none()
)
class Meta:
model = Category
fields = ["id"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance:
self.fields["ordered_products"].queryset = self.instance.cm_products.all()
pass
def save(self):
for sort_order, category in enumerate(self.cleaned_data["ordered_products"]):
category.cm_products.sort_order = sort_order
category.save()
return self.instance
class OrderedModelMultipleChoiceField(forms.ModelMultipleChoiceField):
def clean(self, value):
qs = super().clean(value)
keys = list(map(int, value))
return sorted(qs, key=lambda v: keys.index(v.pk))
Big thanks to the #django IRC channel for the help on this, if anyone suffers the same problem the correct code was as follows:
class ReorderCategoryProductsForm(forms.ModelForm):
ordered_products = OrderedModelMultipleChoiceField(
queryset=OrderedCategoryManagedProduct.objects.all()
)
class Meta:
model = Category
fields = ["id"]
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
if self.instance:
self.fields["ordered_products"].queryset = self.instance.cm_products.all()
pass
def save(self):
for sort_order, ocmp in enumerate(self.cleaned_data["ordered_products"]):
ocmp.sort_order = sort_order
ocmp.save()
return self.instance
I have a filter where I need to access the request.user. However, Django-filter does not pass it. I was able to figure out possible FilterSet configuration.
But how to pass current user from view inside FilterSet?
filters.py
class TransationsListFilter(django_filters.FilterSet):
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user')
super(TransationsListFilter, self).__init__(*args, **kwargs)
transaction_date = DateFromToRangeFilter(widget=RangeWidget(attrs {'placeholder': 'DD/MM/YYYY'}))
class Meta:
model = Transations
fields = ['transaction_date']
#property
def qs(self):
return super(TransationsListFilter, self).filter(user=user)
views.py
class TransactionsList(PagedFilteredTableView):
model = Transations
table_class = TransactionsTable
filter_class = TransationsListFilter
formhelper_class = TransationsFormHelper
models.py
class Transations(models.Model):
transaction_date = models.DateField(default=datetime.now, blank=True)
user = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
default = 0
)
I solved my problem with django-crum plugin.
Here is my final code(other files are the same):
filters.py
import django_filters
from django_filters import DateFromToRangeFilter
from django_filters.widgets import RangeWidget
from .models import Transations,Account
from crum import get_current_user
class TransationsListFilter(django_filters.FilterSet):
transaction_date = DateFromToRangeFilter(widget=RangeWidget(attrs={'placeholder': 'DD/MM/YYYY'}))
class Meta:
model = Transations
fields = ['transaction_date']
#property
def qs(self):
parent = super(TransationsListFilter, self).qs
return parent.filter(user=get_current_user())