Каков наилучший способ обработки иерархии различных, но похожих моделей в Django? - PullRequest
2 голосов
/ 12 марта 2020

В чем суть сделки: я создаю сайт, на котором будут оцениваться различные типы объектов, например, рестораны, косметические салоны, автосервис (и многое другое).

В начале я начинаю с одно приложение с Polymorfi c модель :

models.py :

from django.db import models
from users.models import ProfileUser
from django.utils import timezone
from polymorphic.models import PolymorphicModel

class Object(PolymorphicModel):
    author = models.ForeignKey(ProfileUser, on_delete=models.CASCADE)
    title = models.CharField(max_length=300)
    city = models.ForeignKey(City, on_delete=models.CASCADE)
    address = models.CharField(max_length=300)
    phone = models.CharField(max_length=20, default='')
    email = models.CharField(max_length=100, default='')
    site = models.CharField(max_length=100, default='')
    facebook = models.CharField(max_length=100, default='')
    instagram = models.CharField(max_length=100, default='')
    content = models.TextField()
    rating = models.DecimalField(default=10.0, max_digits=5, decimal_places=2)
    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)
    sea_food = models.BooleanField(default=False)
    is_cash = models.BooleanField(default=False)
    is_bank_card = models.BooleanField(default=False)
    is_wi_fi = models.BooleanField(default=False)
    category_en_name = models.CharField(max_length=100, default='restaurants')
    category_bg_name = models.CharField(max_length=100, default='Ресторанти')
    bg_name = models.CharField(max_length=100, default='Ресторант')
    is_garden = models.BooleanField(default=False)
    is_playground = models.BooleanField(default=False)


class SportFitness(Object):
    is_fitness_trainer = models.BooleanField(default=False)
    category_en_name = models.CharField(max_length=100, default='sportfitness')
    category_bg_name = models.CharField(max_length=100, default='Спорт и фитнес')
    bg_name = models.CharField(max_length=100, default='Спорт и фитнес')


class CarService(Object):
    is_parts_clients = models.BooleanField(default=False)
    category_en_name = models.CharField(max_length=100, default='carservice')
    category_bg_name = models.CharField(max_length=100, default='Автосервизи')
    bg_name = models.CharField(max_length=100, default='Автосервиз')

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()
    approved_object = models.BooleanField(default=False)
    admin_seen = models.BooleanField(default=False)
    created_date = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return f"{self.content}"


class Images(models.Model):
    object = models.ForeignKey(Object, default=None, on_delete=models.CASCADE)
    image = models.ImageField(upload_to='attachments',
                              verbose_name='Image')

class ObjectCoordinates(models.Model):
    object = models.ForeignKey(Object, on_delete=models.CASCADE, related_name='coordinates')
    latitude = models.CharField(max_length=60)
    longitude = models.CharField(max_length=60)

Не ' не говоря уже о том, что имя Object неверно, я уже знаю, что:)

Так что все логики c о разных объектах были в одном приложении, и это начало вызывать некоторые проблемы, например :

views.py:

def show_object(request, category, pk, page_num):
    categories = {'restaurants' : 'Restaurant', 'sportfitness' : 'SportFitness', 'carservice' : 'CarService'} # probably this is not good way to do it
    obj = apps.get_model('objects', categories[category]).objects.get(id=pk)

def show_all_objects(request, category, page_num, city=None):
    params_map = {
        'restaurants': Restaurant,
        'sportfitness': SportFitness,
        'carservice': CarService,
    }

    objects = Object.objects.instance_of(params_map.get(category))

и другие проблемы в шаблонах (много блоков if-else) et c.

Итак Я решаю изменить всю структуру и поместить каждую модель в отдельное приложение, так что теперь у меня есть app:restaurants, app:sportfitness, app:carservices, et c. Но это начинает вызывать некоторые проблемы, опять же, как эта модель:

class ObjectCoordinates(models.Model):
    object = models.ForeignKey(Object, on_delete=models.CASCADE, related_name='coordinates')
    latitude = models.CharField(max_length=60)
    longitude = models.CharField(max_length=60)

Все объекты (рестораны, автосервис) имеют координаты карты, поэтому я не уверен, как с этим справиться, с помощью Модель ObjectCoordinates . Если я создам ObjectCoordinates для каждого из них, соответственно таблицу в BD (тогда у меня будет несколько таблиц с разными именами, но с одинаковой структурой, что не очень хорошо, потому что кроме ObjectCoordinates, модели общего доступа и другие распространенные модели, такие как Images и другие, так что в конце у меня будет много таблиц с разными именами и одинаковой структурой). Возможно, мне следует добавить еще один столбец для категории объектов, если я получил две строки с одинаковым идентификатором объектов?

Возможно, изменение ObjectCoordinates и других распространенных моделей на ManyToMany relation предотвратит идентичные таблицы, но я не совсем уверен в этом. Другая проблема в том, что много повторного кода (в представлениях, шаблонах). Кроме того, теперь я не знаю, как получить все объекты (рестораны, автосервисы), когда у них нет общей точки, например, Модель объекта в первом сценарии с Polymorphic Model. Или я должен оставить разные приложения, но создать общую модель для всех объектов и всех их, чтобы наследовать ее.

Вопросы:

  1. Какая структура лучше, первая или вторая один?
  2. Каков наилучший способ реализации такого сайта (структура модели)?
  3. Должен ли я создать общую точку (модель) для всех моделей, которые они унаследуют?

Вот моя третья попытка (обратите внимание, что объект переименован в Venue):

from django.db import models
from users.models import ProfileUser
from django.utils import timezone
from polymorphic.models import PolymorphicModel

# Create your models here.

class City(models.Model):
    name = models.CharField(max_length=20)

    def __str__(self):
        return f"{self.name}"

class Category(models.Model):
    name = models.CharField(max_length=20)
    bg_name = models.CharField(max_length=20, default=None)
    category_bg_name = models.CharField(max_length=100, default=None)

    def __str__(self):
        return f"{self.name}"

class Venue(models.Model):
    author = models.ForeignKey(ProfileUser, on_delete=models.CASCADE)
    title = models.CharField(max_length=300)
    city = models.ForeignKey(City, on_delete=models.CASCADE)
    address = models.CharField(max_length=300)
    phone = models.CharField(max_length=20, default='')
    email = models.CharField(max_length=100, default='')
    site = models.CharField(max_length=100, default='')
    facebook = models.CharField(max_length=100, default='')
    instagram = models.CharField(max_length=100, default='')
    content = models.TextField()
    rating = models.DecimalField(default=10.0, max_digits=5, decimal_places=2)
    created_date = models.DateTimeField(default=timezone.now)
    approved_venue = models.BooleanField(default=False)
    admin_seen = models.BooleanField(default=False)
    venue_category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='category')

    def __str__(self):
        return f"{self.title}"


class VenueFeatures:
    seats = models.IntegerField()
    bulgarian_kitchen = models.BooleanField(default=False)
    italian_kitchen = models.BooleanField(default=False)
    french_kitchen = models.BooleanField(default=False)
    sea_food = models.BooleanField(default=False)
    is_cash = models.BooleanField(default=False)
    is_bank_card = models.BooleanField(default=False)
    is_wi_fi = models.BooleanField(default=False)
    is_garden = models.BooleanField(default=False)
    is_playground = models.BooleanField(default=False)
    is_fitness_trainer = models.BooleanField(default=False)
    is_parts_clients = models.BooleanField(default=False)
    is_hair_salon = models.BooleanField(default=False)
    is_laser_epilation = models.BooleanField(default=False)
    is_pizza = models.BooleanField(default=False)
    is_duner = models.BooleanField(default=False)
    is_seats = models.BooleanField(default=False)
    is_external_cleaning = models.BooleanField(default=False)
    is_internal_cleaning = models.BooleanField(default=False)
    is_engine_cleaning = models.BooleanField(default=False)
    is_working_weekend = models.BooleanField(default=False)
    is_kids_suitable = models.BooleanField(default=False)
    is_working_weekend = models.BooleanField(default=False)
    venue = models.ForeignKey(Venue, on_delete=models.CASCADE, related_name='venue')

class Comment(models.Model):
    venue = models.ForeignKey(Venue, on_delete=models.CASCADE, related_name='comments')
    author = models.ForeignKey(ProfileUser, on_delete=models.CASCADE)
    content = models.TextField()
    rating = models.TextField()
    approved_venue = models.BooleanField(default=False)
    admin_seen = models.BooleanField(default=False)
    created_date = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return f"{self.content}"


class Images(models.Model):
    venue = models.ForeignKey(Venue, default=None, on_delete=models.CASCADE)
    image = models.ImageField(upload_to='attachments',
                              verbose_name='Image')

class VenueCoordinates(models.Model):
    venue = models.ForeignKey(Venue, on_delete=models.CASCADE, related_name='coordinates')
    latitude = models.CharField(max_length=60)
    longitude = models.CharField(max_length=60)

Теперь я не знаю, как использовать Venue с VenueFeatures

Обратите внимание, что объекты являются просто значениями true / false (флажки в форме).

Ответы [ 2 ]

1 голос
/ 22 марта 2020

Хорошо, это, вероятно, лучший способ абстрагировать что-либо как можно больше:

from django.db import models
from users.models import ProfileUser
from django.utils import timezone
from polymorphic.models import PolymorphicModel

# Create your models here.

class City(models.Model):
    name = models.CharField(max_length=20)

    def __str__(self):
        return f"{self.name}"

class Category(models.Model):
    name = models.CharField(max_length=20)
    bg_name = models.CharField(max_length=20, default=None)
    category_bg_name = models.CharField(max_length=100, default=None)
    icon = models.CharField(max_length=40, default=None)

    def __str__(self):
        return f"{self.name}"

class Venue(models.Model):
    author = models.ForeignKey(ProfileUser, on_delete=models.CASCADE)
    title = models.CharField(max_length=300)
    city = models.ForeignKey(City, on_delete=models.CASCADE)
    address = models.CharField(max_length=300)
    phone = models.CharField(max_length=20, default='')
    email = models.CharField(max_length=100, default='')
    site = models.CharField(max_length=100, default='')
    facebook = models.CharField(max_length=100, default='')
    instagram = models.CharField(max_length=100, default='')
    content = models.TextField()
    rating = models.DecimalField(default=10.0, max_digits=5, decimal_places=2)
    created_date = models.DateTimeField(default=timezone.now)
    approved_venue = models.BooleanField(default=False)
    admin_seen = models.BooleanField(default=False)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)

    def __str__(self):
        return f"{self.title}"


class Feature(models.Model):
    name = models.CharField(max_length=100)
    code = models.CharField(max_length=100  )
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    type = models.CharField(max_length=100)

    def __str__(self):
        return f"{self.name}"

class VenueFeatures(models.Model): # ManyToMany Venues <-> Features
    venue = models.ForeignKey(Venue, on_delete=models.CASCADE)
    feature = models.ForeignKey(Feature, on_delete=models.CASCADE)
    value = models.CharField(max_length=255)

class Comment(models.Model):
    venue = models.ForeignKey(Venue, on_delete=models.CASCADE, related_name='comments')
    author = models.ForeignKey(ProfileUser, on_delete=models.CASCADE)
    content = models.TextField()
    rating = models.TextField()
    approved_venue = models.BooleanField(default=False)
    admin_seen = models.BooleanField(default=False)
    created_date = models.DateTimeField(default=timezone.now)

    def __str__(self):
        return f"{self.content}"


class Images(models.Model):
    venue = models.ForeignKey(Venue, default=None, on_delete=models.CASCADE)
    image = models.ImageField(upload_to='attachments',
                              verbose_name='Image')

class VenueCoordinates(models.Model):
    venue = models.ForeignKey(Venue, on_delete=models.CASCADE, related_name='coordinates')
    latitude = models.CharField(max_length=60)
    longitude = models.CharField(max_length=60)

Теперь Features связаны с Categories

Также Venues являются ManyToMany с Features

Я уже связал его с бизнес-логикой c, и она отлично работает.

0 голосов
/ 19 марта 2020

TL; DR Используйте JSONField (я думаю, JSONB автоматически) в PostgreSQL БЕЗ индекса GIN для VenueFeatures вместо создания совершенно новой модели. Postgres прошел долгий путь к NoSQL / неструктурированным БД, и это действительно хорошо. Использование JSONField в вашей модели Venue будет работать очень хорошо. В самом низу я рассказываю о том, как бы я разработал базу данных вашего сайта.

Хотя я ненавижу это говорить, но это может быть работой базы данных № SQL. Обычно каждое приложение использует структурированную RDBM, но вы используете неструктурированные атрибуты. Вы можете попробовать использовать поле PostgreSQL JSONB, но ... вставка всего в одно поле будет утомительной для индекса GIN + кэширование.

Пока я буду игнорировать многие странные практики, такие как при необходимости разделить пару атрибутов, max_length для поля char обычно имеет длину 255 для всех баз данных, что гарантирует, что в таблицах с наибольшим количеством обращений не будет слишком много атрибутов, чтобы кэширование было лучше (т.е. вам не нужно аннулировать кэшируйте каждый раз, когда пользователь обновляет вашу таблицу), Geo Django для вашей системы координат со стандартной проекционной системой Mercator в режиме Postgres Geography, и вы можете использовать наборы вместо dicts (наборы являются итеративными и используют {}, но ничего не повторяется) ...


Держитесь подальше от этой опции: во-первых, я НИКОГДА не рекомендую MongoDB, но это может быть полезно для вас ... до тех пор, пока ваше приложение не станет слишком большим, как в паре миллионов записей ваша система может сломаться.

Другой РЕКОМЕНДУЕМЫЙ параметр - это PostgreSQL JSONB или Django JSONField withO UT индекс GIN (я настоятельно рекомендую не индексировать это поле, поскольку объекты могут часто менять их так, что переиндексация и кэширование сожжут ваш сервер и замедлят работу вашего приложения). Может быть полезно хранить «Особенности» объекта внутри этого поля JSONB, поскольку все суперструктурировано.

Лучше уменьшить количество атрибутов. У вас их тоже много, что может замедлить запросы. Я рекомендую использовать Django -cachalot для кэширования, так как они поддерживают JSONField, что позволяет избежать проблем с большим количеством атрибутов.


Другие рекомендации в целом

Вместо использования по умолчанию = '', просто сделайте blank=True, null=True, так как вы в основном говорите, что пользователю не нужно заполнять поле электронной почты.

Вроде как у вас будет профиль пользователя вместо того, чтобы заполнять ВСЕ ваши Атрибуты внутри основной пользовательской модели, вы хотите разделить данные вашего объекта на разные модели.


Как я бы это разработал:

Так как у вас изначально были эти три объекта просто сделайте из таблицы «Категории» выбор.

from django.contrib.gis.db import models  # This also imports standard models
from django.contrib.postgres.fields import JSONField  # Remember to turn on GeoDjango with PostgreSQL's PostGIS extension
from django.contrib.postgres.indexes import BrinIndex

class Venue(models.Model):
    id = models.BigAutoField(primary_key=True)
    title = models.CharField(max_length=255)
    rating = models.DecimalField(default=10.0, max_digits=5, decimal_places=2)
    created_date = models.DateTimeField(default=timezone.now)
    approved_venue = models.BooleanField(default=False)
    admin_seen = models.BooleanField(default=False)

    VENUE_TYPES = [
        (1, "restaurant"),
        (2, "concert"),
        (3, "art night")
    ]
    category = SmallPositiveIntegerField(choices=VENUE_TYPES)
    location = models.PointField(srid=4326) # mercator projection from GeoDjango. You don't have to use this; you can stick to your old city and address thing

    class Meta:
        indexes = (
            BrinIndex(fields=['category']),  # this is in case you have a LOT of categories later on.
        )

class VenueProfile(models.Model):
    venue = models.OneToOneField(Venue, on_delete=models.CASCADE, primary_key=True)
    misc_features = JSONField()  # This field is for stuff like your restaurant features OR your concert features. You can put whatever you want in there. Just make sure you have a list of features that people have when trying to access the JSON so you don't run into exceptions.
    created_date = models.DateTimeField(auto_now_add=True)
    facebook = models.CharField(max_length=100, blank=True, null=True)
    instagram = models.CharField(max_length=100, blank=True, null=True)
    city = models.ForeignKey(City, on_delete=models.SET_NULL, null=True)  # SET_NULL in case you accidentally delete a city. You don't want to also delete the venue.
    image = models.ImageField(upload_to='attachments',
                              verbose_name='Image')
    # These attributes are universal for ANY venue so that's why they don't need to be in the JSONField

    """
    For the rest of the features, I have no concern EXCEPT for city. Because you're using GeoDjango, you should also use MaxMind's free city database to determine location based on coordinates. That way, you've essentially scraped the need to store the user and such. You could probably save the address field since it could make things easier that a simple coordinate. It's really up to you. You could also use both!
    """

Атрибуты, которые я добавил в модель объекта, по моему мнению, являются САМЫМИ важными вещами, о которых пользователь сразу захочет узнать.

Модель VenueFeature не так уж сильно обновлена. ПРЕМЬЕР за использование Django -cachalot, так как он не так часто изменяется. (50 модификаций в секунду делают недействительными кэши для каждой модификации, это очень хлопотно).

Комментарии в порядке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...