5
\$\begingroup\$

I have been trying to develop online food order application. I have taken the concept of zomato.com where a user or say owner registers his/her restaurant. After adding the restaurant, an executive from the company will fix a meeting with him/her, take pictures and menu of his/her restaurant by himself/herself or ask him/her to mail it. Long story short an admin will list all the menus in his/her restaurant.

Note: A user can have multiple restaurant. A restaurant has multiple menu items.

For such process flow and structure alike Zomato, will this database design fit well? Is my database designed perfectly deeming best practices?

If i have missed any part, please pitch your thought and idea.

restaurant/models.py

class Restaurant(models.Model):
    OPEN = 1
    CLOSED = 2

    OPENING_STATUS = (
        (OPEN, 'open'),
        (CLOSED, 'closed'),
        )

    BREAKFAST = 1
    LAUNCH = 2
    DINNER = 3
    DELIVERY = 4
    CAFE = 5
    LUXURY = 6
    NIGHT = 7

    FEATURE_CHOICES = (
        (BREAKFAST, 'breakfast'),
        (LAUNCH, 'launch'),
        (DINNER, 'dinner'),
        (DELIVERY, 'delivery'),
        (CAFE, 'cafe'),
        (LUXURY, 'luxury dining'),
        (NIGHT, 'night life'),
        )

    MONDAY = 1
    TUESDAY = 2
    WEDNESDAY = 3
    THURSDAY = 4
    FRIDAY = 5
    SATURDAY = 6
    SUNDAY = 7


    TIMING_CHOICES = (
        (MONDAY, 'monday'),
        (TUESDAY, 'tuesday'),
        (WEDNESDAY, 'wednesday'),
        (THURSDAY, 'thursday'),
        (FRIDAY, 'friday'),
        (SATURDAY, 'saturday'),
        (SUNDAY, 'sunday'),
        )
    user = models.ForeignKey(User)
    restaurant_name = models.CharField(max_length=150, db_index=True)
    slug = models.SlugField(max_length=150, db_index=True)
    address = models.CharField(max_length=100)
    city = models.CharField(max_length=100)
    restaurant_phone_number = models.PositiveIntegerField()
    restaurant_email = models.EmailField(blank=True, null=True)
    owner_email = models.EmailField(blank=True, null=True)
    opening_status = models.IntegerField(choices=OPENING_STATUS, default=OPEN)
    email = models.EmailField()
    restaurant_website = models.TextField(validators=[URLValidator()])
    features = models.IntegerField(choices=FEATURE_CHOICES, default=DINNER)
    timings = models.IntegerField(choices=TIMING_CHOICES, default=MONDAY)
    opening_from = models.TimeField()
    opening_to = models.TimeField()
    facebook_page = models.TextField(validators=[URLValidator()])
    twitter_handle = models.CharField(max_length=80, blank=True, null=True)
    other_details = models.TextField()
    available = models.BooleanField(default=True)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)


    class Meta:
        verbose_name = 'restaurant'
        verbose_name_plural = 'restaurants'
        ordering = ('restaurant_name',)
        index_together = (('id','slug'),)


    def __str__(self):
        return self.restaurant_name

    # def get_absolute_url(self):
    #   return reverse('restaurant:restaurant_detail', args=[self.id, self.slug])



class Category(models.Model):
    name = models.CharField(max_length=120,db_index=True) #veg, non-veg
    slug = models.SlugField(max_length=120,db_index=True)

    class Meta:
        ordering=('name', )
        verbose_name = 'category'
        verbose_name_plural = 'categories'

    def __str__(self):
        return self.name



class Menu(models.Model):
    category = models.ForeignKey(Category, related_name="menu")
    restaurant = models.ForeignKey(Restaurant, related_name="restaurant_menu")
    name = models.CharField(max_length=120,db_index=True)
    slug = models.SlugField(max_length=120,db_index=True)
    image = models.ImageField(upload_to='products/%Y/%m/%d', blank=True)
    description = models.TextField(blank=True)
    price = models.DecimalField(max_digits=10,decimal_places=2)
    stock = models.PositiveIntegerField()
    available = models.BooleanField(default=True)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)


    class Meta:
        ordering=('name', )
        index_together = (('id', 'slug'), )
        verbose_name = 'menu'

    def __str__(self):
        return self.name

    # def get_absolute_url(self):
    #   return reverse('restaurant:menu_detail', args=[self.id, self.slug])

orders/models.py

from django.db import models
from restaurant.models import Menu

class Order(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    email = models.EmailField()
    address = models.CharField(max_length=250)
    postal_code = models.CharField(max_length=20)
    city = models.CharField(max_length=50)
    created = models.DateTimeField(auto_now_add=True)
    updated = models.DateTimeField(auto_now=True)
    paid = models.BooleanField(default=False)

    class Meta:
        ordering = ('-created',)

    def __str__(self):
        return 'Order {}'.format(self.id)

    def get_total_cost(self):
        return sum(item.get_cost() for item in self.items.all())



class OrderMenu(models.Model):
    order = models.ForeignKey(Order, related_name='menu')
    menu = models.ForeignKey(Menu, related_name='order_menu')
    price = models.DecimalField(max_digits=10, decimal_places=2)
    quantity = models.PositiveIntegerField(default=1)


    def __str__(self):
        return '{}'.format(self.id)

    def get_cost(self):
        return self.price * self.quantity
\$\endgroup\$
2
  • \$\begingroup\$ You appear to have forgotten the Product model when adding your orders/models.py file. \$\endgroup\$ Aug 16, 2016 at 2:26
  • \$\begingroup\$ Sorry Product model was replaced by Menu model which i forgot to update. \$\endgroup\$ Aug 16, 2016 at 7:17

2 Answers 2

2
\$\begingroup\$

I'm not totally familiar with Zomato, but I assume it's like any review site for restaurants.

I'll state my assumptions from the start, they might be useful for envisioning the models.

  • Restaurants have menus (lunch, dinner, drinks)
  • Menus have food/drink items (what people would be ordering)

You also appear to be mapping out properties of restaurants as well.

  • Owners have restaurants (for handling chains)
  • Restaurants can be open/closed depending on the day of the week (closed on Sundays, weekends only)
  • Restaurants may open/close at different times depending on the day of the week (dinner only on Mon-Thurs, no breakfast on Sunday)

With all of those assumptions stated, you might see some issues within your models.

  • You should not use an IntegerField for a list of items.

    They inherently only allow a single selection, which makes it impossible to mark something like the days of the week that a restaurant is open. You should use a ManyToMany field for storing things like this.

  • You should separate out your hours of operation information from the Restaurant model.

    Right now it's impossible to set different open/close times for different days of the week. While you could just create 12 new fields (14 total - 2 existing) for storing open/close times for each day, you could also store it as a separate model. This allows you to add additional metadata to it in the future.


For logging the hours that a restaurant is open, you are going to want a dedicated model for it. You probably want to include (at minimum) the following fields

  • Opening time - The time that the restaurant opens on that day.
  • Close time - The time that they close at. This may be before the open time.
  • Day of week - An integer field representing the day of the week that the time is for.
  • Restaurant - The restaurant to apply this for, of course.

Your FEATURE_STATUS and OPENING_CHOICES lists clearly represent choices which may have multiple options. You are currently only allowing a single choice for both of these (likely in other places as well), when you probably want to allow multiple choices. The easy solution here would be to create something similar to your Category model, but connect it with a ManyToMany field instead of a ForeignKey.

\$\endgroup\$
2
  • \$\begingroup\$ I am using django 1.10 so i am having the problem with MultipleSelectField that is why i have used integerfield for a list of items in FEATURE_STATUS and OPENING_CHOICES. I will use ManyToMany like you said. But i wanted more idea to add on my database design so i left it so. Thanks for pitching such worthy idea. I am very much content with your answer. \$\endgroup\$ Aug 16, 2016 at 6:01
  • \$\begingroup\$ Could you please once check in my code here github.com/Tushant/Foodie ? I have updated my model as you have said except the index_together which i did not understand. Also my Choice model display awkwardly in the admin panel because i have used ManyToMany as you have said. Should i create two seperate table for FEATURE and OPENING or in same table ? \$\endgroup\$ Aug 16, 2016 at 7:23
2
\$\begingroup\$

Within your models, you have a few fields that you might be able to improve.

  • You should be using a CharField for anything that shouldn't accept line breaks.

    You are using a TextField in combination with a URLValidator a few times. A TextField is designed to be for things which do not have a maximum length, usually things like descriptions. A CharField is better for things which can usually fit into a defined length, like most restaurant URLs.

  • Django has a URLField for storing and validating a single URL, which you probably want.

    It sets the URL validators automatically, and it also handles the custom form field for entering URLs.

  • Your Restaurant.user field may be better named Restaurant.owner, assuming that's who you were pointing to.

    Having clear field names helps later when you are looking at the code, so you can know for sure what user is supposed to be referenced by that field.

  • You don't need to repeat your model name in front of field names (looking at Restaurant.restaurant_name and Restaurant.restaurant_email)

    This can actually cause issues if you don't do this consistently, which you don't appear to be. In your case, you actually have both the Restaurant.email and Restaurant.restaurant_email fields, and I have no idea what the difference is.

  • Twitter handles can be a maximum of 15 characters

    Your model has a twitter_handle field right now which can handle 80 characters, which is 65 more than you really need. It's always good to research constraints of your system, so you can better architect everything.

Additionally, you are using Django's Model Meta options, which is good, but a few can safely be removed.

  • Don't set the verbose_name if it's the same as the model name.

    By default, Django will generate a verbose_name which is the same as your model name. It's used in quite a few places (Django admin, forms, etc.), and usually it gets it right. The one exception is the plural form, which is where most people override verbose_name_plural for things ending in 'y'.

  • Setting index_together instead of waiting.

    There are usually fields where it Just Makes Sense™ to add an index to them, like a slug which will be referenced in a URL. But I suspect you may not need to set a combined index on your slug and your primary key, and I'd recommend waiting to see how your performance is.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks I understand almost all the issues you raised in my design. I did not understand the last part(Setting index_together instead of waiting), though.Could you please explain me a bit? \$\endgroup\$ Aug 16, 2016 at 6:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.