Reputation: 3733
I'm building a simple Django app for reviews of different objects (restaurants, car services, car wash etc).
I started with the app, but soon I faced first problem. Every object has features (but every type of object has different features).
For example:
So I started to build a typical DB implementation with ManyToMany
tables, but then I found Django Model Inheritance, so I implement it in my APP, as you can see:
urls.py:
from django.urls import path
from . import views
urlpatterns = [
path('user/<int:pk>/', views.UserObjectsView.as_view(), name='user-objects'),
path('add/', views.add_object, name='add-object'),
path('<str:category>/<int:pk>/', views.show_object, name='show-object'),
path('all/<str:category>/', views.show_all_objects, name="show-all-objects"),
]
models.py:
from django.db import models
from users.models import ProfileUser
from django.utils import timezone
# Create your models here.
class Object(models.Model):
author = models.ForeignKey(ProfileUser, on_delete=models.CASCADE)
title = models.CharField(max_length=300)
address = models.CharField(max_length=300)
content = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
approved_object = models.BooleanField(default=False)
admin_seen = models.BooleanField(default=False)
def __str__(self):
return f"{self.title}"
class Restaurant(Object):
seats = models.IntegerField()
bulgarian_kitchen = models.BooleanField(default=False)
italian_kitchen = models.BooleanField(default=False)
french_kitchen = models.BooleanField(default=False)
is_garden = models.BooleanField(default=False)
is_playground = models.BooleanField(default=False)
class SportFitness(Object):
is_fitness_trainer = models.BooleanField(default=False)
class CarService(Object):
is_parts_clients = models.BooleanField(default=False)
class BeautySalon(Object):
is_hair_salon = models.BooleanField(default=False)
is_laser_epilation = models.BooleanField(default=False)
class FastFood(Object):
is_pizza = models.BooleanField(default=False)
is_duner = models.BooleanField(default=False)
is_seats = models.BooleanField(default=False)
class CarWash(Object):
is_external_cleaning = models.BooleanField(default=False)
is_internal_cleaning = models.BooleanField(default=False)
is_engine_cleaning = models.BooleanField(default=False)
class Fun(Object):
is_working_weekend = models.BooleanField(default=False)
is_kids_suitable = models.BooleanField(default=False)
class Other(Object):
is_working_weekend = models.BooleanField(default=False)
class Comment(models.Model):
object = models.ForeignKey(Object, on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(ProfileUser, on_delete=models.CASCADE)
content = models.TextField()
rating = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
def __str__(self):
return f"{self.content}"
views.py:
from django.shortcuts import render, redirect
from django.views import generic
from objects.models import Object, ProfileUser, Comment, Restaurant, SportFitness, CarService, BeautySalon, FastFood, CarWash, Fun, Other
from .forms import ObjectForm, CommentForm
from django.contrib import messages
from django.db.models import Avg
import sys, pdb
class AllObjects(generic.ListView):
queryset = Object.objects.all()
template_name = 'show_all_objects.html'
class UserObjectsView(generic.ListView):
template_name = 'user_objects.html'
def get_queryset(self):
user_id = self.kwargs['pk']
return Object.objects.filter(author_id = user_id)
def add_object(request):
if not request.user.is_authenticated:
messages.info(request, 'За да добавите нов Обект, трябва да сте регистриран потребител!')
return redirect('account_login')
form = ObjectForm(request.POST or None)
if form.is_valid():
obj = form.save(commit=False)
obj.author = ProfileUser.objects.get(user=request.user)
obj.save()
messages.success(request, 'Успешно добавихте нов Обект, може да видите вашите обекти във вашия профил!')
return redirect('home')
context = {
'form': form
}
return render(request, "add_object.html", context)
def show_object(request, pk):
obj = Object.objects.get(id=5)
if request.method == 'POST':
user = request.user
author = ProfileUser.objects.get(user=user)
comment = Comment()
comment.object = obj
comment.author = author
comment.content = request.POST.get('content')
comment.rating = request.POST.get('rating')
comment.save()
form = CommentForm()
reviews_count = Comment.objects.filter(object_id=pk).count()
rating = Comment.objects.filter(object_id=pk).aggregate(Avg('rating'))['rating__avg']
context = {
'form': form,
'object': obj,
'reviews_count': reviews_count,
'rating': rating
}
return render(request, "show_object.html", context)
def show_all_objects(request, category):
categories = {'restaurants' : 'Restaurant', 'sportfitness' : 'SportFitness', 'carservice' : 'CarService', 'beautysalon' : 'BeautySalon', 'fastfood' : 'FastFood', 'carwash' : 'CarWash', 'fun' : 'Fun', 'other' : 'Other'}
objects = eval(categories[category]).objects.all()
context = {
'object_list': objects,
}
return render(request, 'show_all_objects.html', context)
Everything was fine until I had to show objects for each category, like this (I'm using restaurants instead of Restaurant, because url looks much better):
<a href="{% url 'show-all-objects' category='restaurants' %}" class="utf_category_small_box_part"> <i class="im im-icon-Chef"></i>
<h4>Ресторантии</h4>
<span>22</span>
</a>
<a href="{% url 'show-all-objects' category='sportfitness' %}" class="utf_category_small_box_part"> <i class="im im-icon-Dumbbell"></i>
<h4>Спортни и фитнес</h4>
<span>15</span>
</a>
You can check show_all_objects
function, so I did it with eval()
:
categories = {'restaurants' : 'Restaurant', 'sportfitness' : 'SportFitness', 'carservice' : 'CarService', 'beautysalon' : 'BeautySalon', 'fastfood' : 'FastFood', 'carwash' : 'CarWash', 'fun' : 'Fun', 'other' : 'Other'}
objects = eval(categories[category]).objects.all()
It was fine, but then I faced this problem again. When I want to show an object with its features in show_object()
-method (you can check it in code above), I can get the object, but I cannot get restaurant
for example, or car wash
etc:
def show_object(request, pk):
obj = Object.objects.get(id=5)
Now I have the object, but I cannot get the exact object. I get the same problem again when I want to show a different form for each type of object (for example restaurants should have a form with checkboxes with its features, carwashes form with checkboxes for its features etc).
P.S: Based on @Saawhat answer, he recommends me to use eval()
in show_object()
function, but I foreach all ojbects in template, so I cannot pass param like category to it:
<div class="row">
{% for object in object_list %}
<div class="col-lg-12 col-md-12">
<div class="utf_listing_item-container list-layout"> <a href="{% url 'show-object' category='' pk=object.id%}" class="utf_listing_item">
<div class="utf_listing_item-image">
<img src="{% static 'core/images/utf_listing_item-01.jpg' %}" alt="">
<span class="like-icon"></span>
<span class="tag"><i class="im im-icon-Hotel"></i> Hotels</span>
<div class="utf_listing_prige_block utf_half_list">
<span class="utf_meta_listing_price"><i class="fa fa-tag"></i> $25 - $45</span>
<span class="utp_approve_item"><i class="utf_approve_listing"></i></span>
</div>
</div>
<div class="utf_listing_item_content">
<div class="utf_listing_item-inner">
<h3>{{ object.title }}</h3>
<span><i class="sl sl-icon-location"></i> {{ object.address }}</span>
<p>{{ object.content }}</p>
</div>
</div>
</a>
</div>
</div>
{% endfor %}
</div>
This row:
<a href="{% url 'show-object' category='' pk=object.id%}"
I cannot pass category, because I don't have one
Upvotes: 3
Views: 587
Reputation: 2607
Consider saving the object type to the database, in your object model, add a foreign key to ContentType
.
from django.contrib.contenttypes.models import ContentType
class Object(models.Model):
# optional, but this design would also work for abstract inheritance
class Meta:
abstract = True
# ...
content_type = models.ForeignKey(ContentType)
def save(*args, **kwargs):
self.content_type = ContentType.objects.get_for_model(self.__class__)
super().save(*args, **kwargs)
Then in your template:
<a href="{% url 'show-object' category=object.content_type.model pk=object.id %}">
You can then find your model in your views like so:
def show_object(request, category, pk):
# swap out `myapp` to your app name
# if you don't care about url being friendly
# you can use content type primary key instead of model name
model = ContentType.objects.get(app_label="myapp", model=category).model_class()
obj = model.objects.get(pk=pk)
# ...
def show_all_objects(request, category):
model = ContentType.objects.get(app_label="myapp", model=category).model_class()
all_objects = model.objects.all()
# ...
Upvotes: 1
Reputation: 758
1: When using inheritance for models put
class Meta:
abstract = True
in your base model, which is Object
in your case so that it doesn't create its own instance/table. So in this case your model will look like
class Object(models.Model):
author = models.ForeignKey(ProfileUser, on_delete=models.CASCADE)
title = models.CharField(max_length=300)
address = models.CharField(max_length=300)
content = models.TextField()
created_date = models.DateTimeField(default=timezone.now)
approved_object = models.BooleanField(default=False)
admin_seen = models.BooleanField(default=False)
class Meta:
abstract = True
def __str__(self):
return f"{self.title}"
On other note rename Object
to something else like BaseModel
or something.
2: When you're calling doing show_object
pass the category/name of the model & query on that, eg:
obj = Object.objects.get(id=5) #Instead of this obj = eval(categories[category]).objects.get(pk=pk) # Do this
3: For your Comment
model, either you add bunch of foreign keys for each of your model or you can use Generic Relation in Django.
4: While showing forms use respective models not Object
model. You anyways have access to all the fields because you're inheriting from it.
I hope I've answered all of your questions, if not let me know.
Update: Regarding you not being able to pass category from template, in your views pass category also to the template
def show_all_objects(request, category):
categories = {'restaurants' : 'Restaurant', 'sportfitness' : 'SportFitness', 'carservice' : 'CarService', 'beautysalon' : 'BeautySalon', 'fastfood' : 'FastFood', 'carwash' : 'CarWash', 'fun' : 'Fun', 'other' : 'Other'}
objects = eval(categories[category]).objects.all()
context = {
'object_list': objects,
'category': category
}
return render(request, 'show_all_objects.html', context)
Now you can access category
in your template file & pass it back.
Upvotes: 5