6

Note: I've since asked this question again given the updates to Django's user model since version 1.5.

I'm rebuilding and making improvements to an already existing Django site and moving it over from Webfaction to Heroku, and from Amazon's SimpleDB to Heroku Postgres (though testing locally on Sqllite3 when developing). A lot of what I'm doing is moving over to use built-in Django functionality, like the Django admin, user authentication, etc.

Conceptually, the site has two kinds of users: Students and Businesses. The two types of users have completely different permissions and information stored about them. This is so much the case that in the original structure of the site, we set up the data model as follows:

Users
    ID (primary_key)
    Business_or_Student ('B' if business, 'S' if student)
    email (unique)
    password (hashed, obviously)
    ...

Students
    ID (Foreignkey on Users)
    <more information>
    ...

Businesses
    ID (Foreignkey on Users)
    <more information>
    ...

This worked pretty well for us, and we had the bare-bones user information in the Users table, and then any more detailed information in the Students and Businesses tables. Getting a user's full profile required something along this pseudocode:

def get_user_profile(id):
    if Users(id=id).Business_or_Student = 'B':
        return Businesses(id=id)
    else:
        return Students(id=id)

In moving over, I've found that Django's built-in User object has pretty limited functionality, and I've had to extend it with a UserProfile class I've created, and then had additional Student and Business tables. Given all of the patching I'm doing with this in the Django admin, and being relatively unfamiliar with Django models since I always did it differently, I'm not sure if this is the best way to go about it, or if I should just stick all of the information for businesses and students in the UserProfile table and just differentiate the two with different groups, or if there's even some way to do this all in the built-in User object.

Since businesses and students also have different interfaces, I'm seriously considering setting up the two as different apps within my Django project, and so separating their views, models, etc. entirely. That would look something like:

MyProject/
    MyProject/ (project folder, Django 1.4)
    mainsite/
    students/
    businesses/

One of my biggest concerns is with the Django Admin. In extending User, I already had to add the following code:

class UserProfileInline(admin.StackedInline):
    model = UserProfile
    can_delete = False
    verbose_name_plural = 'profile'

class UserAdmin(UserAdmin):
    inlines = (UserProfileInline, )

However, I would like the information for the Business or Student aspects of the user to show up in the Django admin when that User is pulled up, but the ForeignKey part of the model is in the Student and Business model since every Student/Business has a User but every User has only one Student or one Business object connected with it. I'm not sure how to add a conditional Inline for the Admin.

Question: Given this structure and these concerns, what is the best way to set up this site, particularly the data model?

Community
  • 1
  • 1
jdotjdot
  • 16,134
  • 13
  • 66
  • 118

2 Answers2

5

This is not a complete solution, but it will give you an idea of where to start.

  • Create a UserProfile model in mainsite. This will hold any common attributes for both types of users. Relate it to the User model with a OneToOne(...) field.
  • Create two more models in each app, (student/business), Business and Student, which have OneToOne relationships each with UserProfile (or inherit from UserProfile). This will hold attributes specific to that type of users. Docs: Multitable inheritance / OneToOne Relationships
  • You may add a field in UserProfile to distinguish whether it is a business or student's profile.

Then, for content management:

  • Define the save() functions to automatically check for conflicts (e.g. There is an entry for both Business and Student associated with a UserProfile, or no entries).
  • Define the __unicode__() representations where necessary.
Rohan
  • 52,392
  • 12
  • 90
  • 87
  • I started out with doing this, but then I switched the `OneToOne` relationship between `Student` and `UserProfile` to `Student` and `User` because the Django admin wouldn't recognize the username/email of the `UserProfile` objects, since those were stored in `User` rather than `UserProfile`. When adding a new Businesses, the `OneToOne` relationship dropdown would simply list `UserProfile Object` over and over again until I changed it to `User`, when it started listing usernames. I'm worried about stuff like that happening in other areas. Do you know how to deal with that? – jdotjdot Oct 17 '12 at 04:22
  • 1
    Yes, `UserProfile` links to `User`, which django uses to represent an user. To mask your dropdown problem, add `__unicode__()` method in `UserProfile` to return `self.user.username`. This is show username in dropdown, however will still link to `UserProfile`. – Rohan Oct 17 '12 at 04:27
  • Thank you, that last bit was genius. I'm going to leave the question open for a few days to see what other responses I get, but you've been incredibly helpful. – jdotjdot Oct 17 '12 at 04:32
4

I hope I understood your problem... maybe this can work? You create a abstract CommonInfo class that is inherited in into the different Sub-classes (student and businesses)

class CommonUser(models.Model):      
    user = models.OneToOne(User)
    <any other common fields>

    class Meta:
        abstract = True


class Student(CommonUser):
    <whatever>

class Business(CommonUser):
    <whatever>

In this case the models will be created in the DB with the base class fields in each table. Thus when you are working with Students you run a

students = Students.objects.get.all() 

to get all your students including the common information.

Then for each student you do:

for student in students:
    print student.user.username

The same goes for Business objects.

To get the student using a user:

student = Student.objects.get(user=id)

The username will be unique thus when creating a new Student or Business it will raise an exception if an existing username is being saved.

Forgot to add the link

Mikael
  • 3,148
  • 22
  • 20