(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 SubClientUser
s.
- 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 Subscription
s are started, or a Credit
for which one-time Purchase
s 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?