Question

Building an eCommerce site that exclusively sells shirts, books, and albums to get a feel for different models. Two things that are throwing me off are:

1.) I'm not sure if I'm creating too much redundancy with my models if so how do I minimize it. For example, for music albums, I'm creating a model for albums, artists, and tracks and in each of those models I reference the other models multiple times.

2.) In regards to my ManyToManyFields, by referencing a model that's not defined until further down in models.py, python manage.py syncdb is throwing up an error that I have no defined the relationships. This is explained further in this SO question: Django says my model is not defined

Here is my models.py:

from django.db import models

#Artist, Album, and Tracks are tables for music albums

class Artist(models.Model):
    artist_name = models.CharField(max_length=100, null=False, blank=False)
    short_bio = models.CharField(max_length=2000, null=False, blank=False)
    albums = models.ManyToManyField(Album)
    tracks = models.ManyToManyField(Track)
    added = models.DateTimeField(auto_now=True, auto_now_add=False)
    updated = models.DateTimeField(auto_now=False, auto_now_add=True)

class Album(models.Model):
    album_name = models.CharField(max_length=150, null=False, blank=False)
    artists = models.ManyToManyField(Artist)
    tracks = models.ManyToManyField(Track)
    active = models.BooleanField(default=True)
    slug = models.SlugField(null=True, blank=True)
    added = models.DateTimeField(auto_now=True, auto_now_add=False)
    updated = models.DateTimeField(auto_now=False, auto_now_add=True)

    def __unicode__(self):
        return self.album_name

class Track(models.Model):
    track_name = models.CharField(max_length=200, null=False, blank=False)
    artists = models.ManyToManyField(Artist)
    album_name = models.ManyToManyField(Album)

class AlbumImage(models.Model):
    album_name = models.ForeignKey(Album)
    description = models.CharField(max_length=200, null=True, blank=False)
    main = models.BooleanField(default=False)
    image = models.ImageField(upload_to='albums/images/')
    added = models.DateTimeField(auto_now=True, auto_now_add=False)
    updated = models.DateTimeField(auto_now=False, auto_now_add=True)


#Book, BookGenre, Author tables for books

class Book(models.Model):
    title = models.CharField(max_length=200, null=False, blank=False)
    authors = models.ManyToManyField(Author)
    pages = models.PositiveSmallIntegerField(null=True, blank=True)
    genre = models.ManyToManyField(BookGenre)
    active = models.BooleanField(default=True)
    slug = models.SlugField(null=True, blank=True)
    added = models.DateTimeField(auto_now=True, auto_now_add=False)
    updated = models.DateTimeField(auto_now=False, auto_now_add=True)

    def __unicode__(self):
        return self.title

class Author(models.Model):
    author_name = models.CharField(max_length=50, null=False, blank=False)
    short_bio = models.CharField(max_length=2000, null=True, blank=True)
    titles = models.ManyToManyField(Book)
    genre = models.ManyToManyField(BookGenre)
    added = models.DateTimeField(auto_now=True, auto_now_add=False)
    updated = models.DateTimeField(auto_now=False, auto_now_add=True)

class BookGenre(models.Model):
    genre = models.CharField(max_length=50, null=False, blank=False)

class BookImage(models.Model):
    title = models.ForeignKey(Book)
    description = models.CharField(max_length=200, null=True, blank=False)
    main = models.BooleanField(default=False)
    image = models.ImageField(upload_to='books/images/')
    added = models.DateTimeField(auto_now=True, auto_now_add=False)
    updated = models.DateTimeField(auto_now=False, auto_now_add=True)


#Shirt and Colors are for tables exclusively for shirts

class Shirt(models.Model):
    title = models.CharField(max_length=50)
    description = models.CharField(max_length=2000, null=True, blank=False)
    styles = models.ManyToManyField('ShirtStyle.style')
    sz_xs = models.PositiveSmallIntegerField(null=False, blank=False)
    sz_sm = models.PositiveSmallIntegerField(null=False, blank=False)
    sz_md = models.PositiveSmallIntegerField(null=False, blank=False)
    sz_lg = models.PositiveSmallIntegerField(null=False, blank=False)
    sz_xl = models.PositiveSmallIntegerField(null=False, blank=False)
    active = models.BooleanField(default=True)
    slug = models.SlugField(null=True, blank=True)
    added = models.DateTimeField(auto_now=True, auto_now_add=False)
    updated = models.DateTimeField(auto_now=False, auto_now_add=True)

    def __unicode__(self):
        return self.title

class ShirtStyle(models.Model):
    style = models.CharField(max_length=50, null=False, blank=False)
    title = models.ForeignKey('Shirt.title')

class ShirtImage(models.Model):
    title = models.ForeignKey(Shirt)
    description = models.CharField(max_length=200, null=True, blank=False)
    main = models.BooleanField(default=False)
    image = models.ImageField(upload_to='shirts/images/')
    added = models.DateTimeField(auto_now=True, auto_now_add=False)
    updated = models.DateTimeField(auto_now=False, auto_now_add=True)

Thanks for your time.

Was it helpful?

Solution

Great start, but, as you've guessed, you've got a little too much redundancy in your models. I would offer the following tips:

1) Don't use ManyToManyField as much.

ManyToMany should be used where each model is related to many of the other. This isn't really the case for some of your models. For example, while each Album has many Tracks, the reverse probably isn't true; each Track belongs to one, and only one, Album. "Smells Like Teen Spirit" is a Track on the Album "Nevermind", but it's not a Track on any other Album. Thus, you should use a ForeignKey instead.

class Track(models.Model):
    track_name = models.CharField(max_length=200, null=False, blank=False)
    album_name = models.ForeignKey(Album)

An exception here would be if a Track actually would be on multiple Albums, i.e. if it was also on a "Greatest Hits" Album or something in addition to "Nevermind".

2) Don't repeat relations

In the above example, we've connected each Track to a Album through a ForeignKey. We don't need to do the opposite. That is, Album does not need an explicit field to keep track of all of its Tracks. You can do that when you query. The Django docs on making queries that span relationships are here.

This also has a further implication. You'll likely be connecting your Album to an Artist through a ForeignKey. Because you can span relationships, you don't need to explicitly connect Tracks and Artists. Thus, a simple example of the three models should look like this:

from django.db import models

#Artist, Album, and Tracks are tables for music albums

class Artist(models.Model):
    artist_name = models.CharField(max_length=100, null=False, blank=False)
    short_bio = models.CharField(max_length=2000, null=False, blank=False)
    added = models.DateTimeField(auto_now=True, auto_now_add=False)
    updated = models.DateTimeField(auto_now=False, auto_now_add=True)

class Album(models.Model):
    album_name = models.CharField(max_length=150, null=False, blank=False)
    artist = models.ForeignKey(Artist)
    active = models.BooleanField(default=True)
    slug = models.SlugField(null=True, blank=True)
    added = models.DateTimeField(auto_now=True, auto_now_add=False)
    updated = models.DateTimeField(auto_now=False, auto_now_add=True)

    def __unicode__(self):
        return self.album_name

class Track(models.Model):
    track_name = models.CharField(max_length=200, null=False, blank=False)
    album_name = models.ForeignKey(Album)

This is also subject to the caveat that if you'll be connecting an Album to multiple Artists, you'll need to tweak it somewhat. This would be like if you separated the band members out individually, i.e. "Abbey Road" is an Album for four different Artists: John, Paul, George, and Ringo.

3) Defining the model error

The problem you noted in (2) relating to models should go away once you stop repeating relations. Basically you can't use a model as a relation until you've already defined it. This was causing a problem as you were doing ManyToMany on both models to each other. One of them has to come before the other, which will result in an import problem. You could also set null=True if you wanted to use one before it was defined, but there's really no need in this case.

Let me know if any of this is unclear or if you have any further questions.

EDIT:

Additional tip:

If you're going to be using added and modified fields on many models, you might want to use the django-model-utils package. Docs here. You can subclass a TimeStampedModel that automatically makes created and modified fields. See here.

Thus:

from model_utils.models import TimeStampedModel

class Artist(TimeStampedModel):
    artist_name = models.CharField(max_length=100, null=False, blank=False)
    short_bio = models.CharField(max_length=2000, null=False, blank=False)
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top