0

So, I am a beginner and I'm coding a django application that has a 3 user types in total but to make the code readable and understandable I'm just gonna assume we have 2 user types. One is a doctor and one is a patient.

I've created this custom user so far:

in models.py

class User(AbstractUser):
    is_doc = models.BooleanField(default=False)
    is_patient = models.BooleanField(default=False)

    objects = UserManager()

manager.py

class UserManager(BaseUserManager):

    def create_user(self, email, password=None):

        if email is None:
            raise TypeError('Users must have an email address.')

        user = self.model(email=self.normalize_email(email))
        user.set_password(password)
        user.save()

        return user
    def create_patient(self, email, password):
        if password is None:
            raise TypeError('Must have a password')
        
        user = self.model(email=self.normalize_email(email))
        user.set_password(password)
        user.is_patient = True
        user.save()

    def create_superuser(self, email, password):

        if password is None:
            raise TypeError('Superusers must have a password.')

        user = self.create_user(email, password)
        user.is_superuser = True
        user.is_staff = True
        user.save()

        return user

Patient Model is as follows:

class Patient(models.Model):
    user = models.ForeignKey(User, on_delete=models.CASCADE)
    first_name = models.CharField(max_length=70)
    last_name = models.CharField(max_length=70)
    address = models.CharField(max_length=210)
    phone = models.CharField(max_length=15)
    email = models.EmailField(max_length=110)
    password = models.CharField(max_length=65)

    def __str__(self):
        return self.first_name + " " + self.last_name

My form for Patient:

class PatientForm(forms.ModelForm):

    class Meta:
        model = Patient
        fields = ['first_name', 'last_name', 'address', 'phone', 'email', 'password']

        widgets = {
            'first_name': forms.TextInput(attrs={'placeholder': 'First Name'}),
            'last_name': forms.TextInput(attrs={'placeholder': 'Last Name'}),
            'address': forms.TextInput(attrs={'placeholder': 'Address'}),
            'phone': forms.TextInput(attrs={'placeholder': 'Phone Number'}),
            'email': forms.EmailInput(attrs={'placeholder': 'Email'}),
            'password': forms.PasswordInput(attrs={'placeholder': 'Password (Minimum Length: 8)'}),
        }

Current Views.py:

def RegPatient(request):
    if request.method == "POST":
        form = PatientForm(request.POST)
        if form.is_valid():
            FName = form.cleaned_data['first_name']
            LName = form.cleaned_data['last_name']
            C_Address = form.cleaned_data['address']
            C_Phone = form.cleaned_data['phone']
            C_Email = form.cleaned_data['email']
            C_Password = form.cleaned_data['password']
            Reg = Patient(first_name= FName, last_name= LName, address= C_Address, phone= C_Phone, email= C_Email, password= C_Password)
            Reg.save()
            return HttpResponseRedirect('/Login/')

        else:
            for field in form.errors:
                form[field].field.widget.attrs['class'] += ' is-invalid'
            return render(request, 'Home/RegPatient.html', {'form': form})
    
    else:
        form = PatientForm()
        return render(request, 'Home/RegPatient.html', {'form': form})

I have separate signups for patient and doctor and don't want a user type field in my form so I have all the detail fields in my form except user type. How should my view look like so that it saves Patient with a user type is_patient? and am I missing something out? Cause I've tried to edit it so many times, tried getting help from Django Docs but I'm still stuck.

UmairKay
  • 83
  • 8

1 Answers1

1

First, lets talk about the Manager and Models. The Manager you are using is not really necessary, its possible to just set up the field on User object creation. As for the Model you can use model inheritance to have an extended Patient model based on your User model:

class User(AbstractUser):
    is_doctor = models.BooleanField(default=False)
    is_patient = models.BooleanField(default=False)

class Patient(User):
    address = models.CharField(max_length=210)
    phone = models.CharField(max_length=15)

    class Meta:
        verbose_name_plural = "Patients"

Although, I would say the right approach to your problem is to use Django authentication groups, instead of adding fields to your User model.

As for the signup form, it should contain confirmation and validation of the password and the username field unless you are doing a login with email. To hold the information of is_patient field, use an HiddenInput (I couldn't replicate it with 'ModelForm' will need to dig further into that.):

forms.py

from django.contrib.auth.hashers import make_password
from django.forms import ValidationError

class PatientSignUpForm(forms.Form):
    username = forms.CharField(
        required=True, 
        widget=forms.TextInput(attrs={'placeholder': 'username'}))

    first_name = forms.CharField(
        required=True, 
        widget=forms.TextInput(attrs={'placeholder': 'first name'}))

    last_name = forms.CharField(
        required=False, 
        widget=forms.TextInput(attrs={'placeholder': 'last name'}))

    email = forms.EmailField(
        required=True, 
        widget=forms.TextInput(attrs={'placeholder': 'email'}))

    address = forms.CharField(
        required=True, 
        widget=forms.TextInput(attrs={'placeholder': 'address'}))

    phone = forms.CharField(
        required=True, 
        widget=forms.TextInput(attrs={'placeholder': 'XXX-XXXXXXXXX'}))

    password = forms.CharField(required=True, widget=forms.PasswordInput)

    password_confirmation = forms.CharField(required=True, widget=forms.PasswordInput)

    is_patient = forms.BooleanField(widget=forms.HiddenInput)

    def clean(self):
        password = self.cleaned_data['password']
        password_confirmation = self.cleaned_data['password_confirmation']

        if len(password) < 8 or len(password_confirmation) < 8:
            raise ValidationError("Passwords must have at least 8 characters")
        elif password != password_confirmation:
            raise ValidationError("Passwords must be equal")
        else:
            self.cleaned_data['password'] = make_password(self.cleaned_data['password_confirmation'])
            del self.cleaned_data['password_confirmation']
            return self.cleaned_data

Lastly, in views.py create an instance of PatientSignUpForm with an initial value for is_patient field.

from app import models, forms

def patient_signup(request):
    if request.method == 'POST':
        form = forms.PatientSignUpForm(request.POST)
        if form.is_valid():
            models.Patient.objects.create(**form.cleaned_data)
            return redirect('')

    else:
        form = forms.PatientSignUpForm(initial={'is_patient': True})

    return render(request, 'patient_signup.html', {'form': form})
Niko
  • 3,012
  • 2
  • 8
  • 14
  • Wow, clearly explained! I'll try this approach right now. Just one more thing, lets say `User` has a field `first_name` and I later create another User type by inheriting User but I want the field `hospital_name` instead of `first_name` what do I do then? – UmairKay Dec 30 '22 at 09:24
  • Honestly, what you are asking doesn't make much sense, a `Hospital` would be another different entity than `Patient / Doctor`, so it would have a related FK. But it seems you are asking if there is a way to rename inherited fields and for that question I do not have an answer. – Niko Dec 30 '22 at 10:12
  • Oh I see, so what would be a good approach if I need to add another user type that does not have `first_name` and `last_name` field? – UmairKay Dec 30 '22 at 12:40
  • Btw, even after `del self.cleaned_data['password_confirmation']` Django still throwing an error about unexpected argument password_confirmation – UmairKay Dec 30 '22 at 13:12
  • In case of deeper customization make use of `AbstractBaseUser`. Related to your problem `print(form.cleaned_data)` after validation to make sure `password_confirm` is not inside the dictonary. – Niko Dec 30 '22 at 13:43
  • @Nike Alright thanks, and yes I did that, `password_confirm` is still in the dictionary after `del` – UmairKay Dec 30 '22 at 13:45
  • That is awkward, it shouldn't. But, just [remove](https://stackoverflow.com/a/11277439/7100120) it and create the object normally `models.Patient.objects.create(**form.cleaned_data)` – Niko Dec 30 '22 at 13:48