Setting up a database structure where a single person can be responsible for one (themselves) or more people (employees, assistants, staff etc.)

I have followed the User model guide from Two Scoops of Django 3.x.

  • User is a custom user model.
  • It has a type property. Then I build 3-4 proxy models to the User model for each type, with a custom model manager so that only the required type users are returned/handled.

For Clients, I have a ClientUser corresponding to the accounts.user table, and a ClientProfile class responsible for storing additional, type-specific data about this user.

Now here is where I want to build a relationship as follows:

ClientUser pays for subscriptions and manages any sub-client users, where SubClientUsers access the system and make use of the service.

Billing related info is mostly stored with ClientProfile, and personal preferences, settings, email, phone number etc. for each service consumer is stored in SubClientProfile.

SubClientUser has a foreign key to ClientUser – it can be 1:1 throughout the lifetime of a client user (one person signs up and manages their own billing as well as uses the service), or 1:many (a person belongs to an organization which manages them and pays for their subscription and access).

I feel like I’m doing something… “wrong” here? Is SUBCLIENT a new type of User, alongside CLIENT?

I think sharing the models and their relationship fields would be beneficial for us.

Can you elaborate on this, such as why do you feel this is wrong? Is there some code that has a “smell” to it or maybe a design? Could you share it?

(Oh hi! We do follow each other on Twitter – recognized your picture. Small world!)

User model.

Custom user model.

type property is set to a choice field. Choices are derived from:

class Types(models.TextChoices):
    # This is a tuple without the round brackets.
    CLIENT = "CLIENT", "Client"
    ASSISTANT = "ASSISTANT", "Assistant"
    CUSTOMER_SUPPORT = "CUSTOMER_SUPPORT", "Customer Support"
    MANAGER = "MANAGER", "Manager"
type = models.CharField(
    _("Type"), max_length=50, choices=Types.choices, default=Types.CLIENT
)

Proxy models for User.

class ClientUserManager(UserManager):
    def get_queryset(self, *args, **kwargs):
        results = super().get_queryset(*args, **kwargs)
        return results.filter(type=User.Types.CLIENT).order_by("pk")


class ClientUser(User):
    """
    This is a proxy model to the User class. Primarily,
    it gives you a nicer interface to interact with
    `CLIENT` type users, and avoids any "silly" errors
    down the line.
    """

    base_type = User.Types.CLIENT

    # A simple abstraction to ensure ClientUser queries
    # return only users of 'client' type.
    objects = ClientUserManager()

    class Meta:
        # This means that no table will be created for this
        # model in the database. By being a proxy model,
        # we are able to add additional methods (and methods
        # only) to this model.
        proxy = True
        verbose_name = _("Client")
        verbose_name_plural = _("Clients")

Repeat for all types of users: AssistantUser, CustomerSupportUser, ManagerUser. All of them are proxy to the custom User model.

Profile data.

class ClientProfile(UserProfileMixin, TimeStampedModel):
    """
    Profile model for storing extra data about a certain
    type of user. In this case, the user is a client.
    In either case, the owner field is named 'user'.
    """

    user = models.OneToOneField(
        to=ClientUser,
        on_delete=models.SET_NULL,
        null=True,
        related_name="client_set",
        help_text="This is the client to whom the profile belongs.",
    )

Repeat for all types of users: AssistantProfile, CustomerSupportProfile, ManagerProfile.


Now, setting up that relationship where a person subscribes to our application but 90% of them will be singing up themselves, paying themselves, and using the service themselves.

However, I want to keep the option for the future where 1 person (representing an organization, a club, a company, etc.) can sign up, set up billing, and finally be responsible for SubClientUsers who will be using the service.

Can you elaborate on this, such as why do you feel this is wrong? Is there some code that has a “smell” to it or maybe a design? Could you share it?

The design is as follows for users:

  • ClientUser is the building block.
  • SubClientUser is anyone who has access to the system, while a ClientUser is anyone who pays for it on behalf of one or more SubClientUsers.
  • A SubClientUser stores their profile information in SubClientProfile (things like contact number, address, communication preferences, age, etc.), while a ClientUser stores their billing details in ClientProfile (billing address, payment methods, etc.)

And for billing:

  • A Product can be a Plan for which Subscriptions are started, or a Credit for which one-time Purchases are made.
  • Any incoming and outgoing transactions are stored in a Transactions table. Refunds, purchases, sales, etc. Everything financial goes here.

The reason it feels weird to me, I think, is because in the case where 90% users are self-paying and using, billing data and personal profile data is split in two places. It has the potential to make the admin area confusing, as well as the client area code to be spaghetti over time?

Okay, so thinking more about this, yes, SUBCLIENT will become a new user type, and I’ll follow it up with a SubClientUser class that is a proxy class to the custom User model. Similar to what I’ve been doing already.

This is because no matter whether a client or a sub-client, they both will need separate login details for themselves.

However, a problem crops up in this design. When a ClientUser signs up for themselves, the SubClientUser entity is completely pointless.

Have you considered adding an organization type model to handle the grouping of users together?