Create a CV/Resume Builder using the Django Rest Framework (part 3)

Create a CV/Resume Builder using the Django Rest Framework (part 3)

ยท

8 min read

Draw the plan, get to work

~ Laptop and the studious Lady

Table of Content

  1. Introduction
  2. What is a model?
  3. Django Model fields types
  4. Building the CV/Resume models in Django
  5. Uploading image via Django Admin site

Introduction
Recall our Database Entity-Relation diagram in Part 1, we shall be using the design to create our models in Django project named CVeate which we setup in Part 2.

Fresher:

In cv app, we can see the folder structure

|--- cveate
|--- cv
       |--- __init__.py
       |--- admin.py
       |--- apps.py
       |--- models.py
       |--- tests.py
       |--- views.py

From the ER-diagram, we would create the following models in cv/models.py:

  • Resume houses the basic resume information such as the user it belongs to.
  • EmploymentHistory stores employment information including Internships and Apprenticeships.
  • EducationHistory stores education information.
  • Qualification stores certification ( or testimonial) acquired.
  • Skill stores technical and non-technical skills.
  • Referee stores referee which is usually expected on resumes.
  • WebLink stores social media links such as LinkedIn URL.
  • ResumeTemplate stores the various HTML templates.

When we think of a resume, we think of a first name, last name email, job title, and professional summary or career objectives. Those are key fields that cannot be left out when filling a typical resume.

To begin, we first need a top-down understanding of how models work.

What is a model?

A model can be imagined as a 'sample' of how things should be represented. Let's imagine you visit a clothing store, one usually sees a mannequin which is dressed in a sample of clothing. This is done to help you visualize the same clothing on yourself or someone else with a similar body type.

Model gives you a glimpse of what to expect. Same as actual models who walk at fashion shows.

In Django and back-end development in general, models act as a guide to what type of data is stored and what Django (or whichever programming language) should expect as inputs.

In Django, models have different data types as expected inputs, from characters to date to boolean (true/false) to URL input types.

Consider a spreadsheet named "Resume" that has columns with different names and different types of inputs,

excel-sheet-1.png

In each record (row), one cannot only put a value that agrees with that specific field when data validation is enabled. If you try to put a number in a text field, you will be told that cannot be accepted.

excel-validation.png

The above is a visual example of what models do. There can be different constraints (rules) that each field can have. For example, one can make sure that a field cannot be left empty. Hence, a value must be provided by the user, in Django models, you can specify the same thing by setting blank=False although Django makes sure a value is provided by default.

Similarly, one can choose to allow users to skip an entry on a spreadsheet. This can be demonstrated by adding blank=True on a field name in Django. However, for that to take effect, one needs to make sure that the database itself is aware of that change.

By this, one must either provide a default value which will be sent along with other records to the database or set null=True which sends a value defined as null to the database to show no entry in that field.

Therefore, the database becomes aware that the particular field can have no value if set null=True in the model. And the database is given a standard or default value if default=<a standard value> is set.

Django Model Field Types

We usually use different types of model fields for different data types. For example to represent primitive data types, Django models have the following:

  1. Short to medium length strings - models.CharField(max_length=<add a number>)
  2. Long text - models.TextField()
  3. Numbers - models.IntegerField()
  4. Decimal number - models.DecimalField(decimal_place=<add a decimal placement>)
  5. Boolean - models.BooleanField()
  6. Floating point number - models.FloatField()

Some other frequently used field types are:

  1. Email - models.EmailField()
  2. URL - models.URLField()
  3. IP Address - models.IPAddressField()
  4. Image - models.ImageField()
  5. File - models.FileField()
  6. Date and Time - models.DateTimeField()
  7. Date only - models.DateField()

Model types used to establish relationships between other models are:

  1. One-to-one relationship - models.OneToOne() enforces uniqueness on the field name it is applied to.
  2. Foreign relationship - models.ForeignKey() is almost the same as one-to-one, the only difference is foreign relationship is NOT unique.
  3. Many-to-many relationship - models.ManyToMany() is a two-way relationship.

You can go through django documentation to learn more. In summary, model fields are mapping to real-world data types.

Building the CV/Resume models in Django

Since we need to identify the user who owns a resume, we establish a foreign relationship to Django's default User model.

This means that a user can have more than one resume.

from django.contrib.auth.models import User

class Resume(models.Model):

    '''Resume model'''
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='resumes')
    job_title = models.CharField(max_length=200)
    first_name = models.CharField(max_length=200)
    last_name = models.CharField(max_length=200)
    email = models.CharField(max_length=200)
    professional_summary = models.TextField()
    resume_template = models.ForeignKey(
        ResumeTemplate,
        on_delete=models.CASCADE
    )

The above code block looks similar to the spreadsheet image showed previously.

Notice the additions to models.ForeignKey for resume_template field. The on_delete answers the question what should happen to this field when the model it points to gets deleted?. Possible answers are;

  • models.CASCADE: wherever the referenced model instance is mentioned, delete it.
  • models.SET_NULL: do not delete, instead make the field nullable. Note that this requires the null=True set on that field to prevent SQL constraint kicking in.
  • models.DO_NOTHING: does nothing.
  • models.SET_DEFAULT: falls back to the default value set on that field. Similar to SET_NULL, this requires default=<some default value> set.
  • models.PROTECT: prevent deletion of the referenced object.

related_name creates a reverse relation between the referenced model instance and the current model. For example in the Resume model, the User model can reference the Resume model without doing something like this; Resume.objects.filter(user=<a user ID>). Using related_name, it can be done this way; user.resumes.all().

One can note that the Resume Template is a foreign key. We need to create the Resume Template model above the newly created Resume model.

For the ResumeTemplate model, we shall give each template a name and a sample image which shall be uploaded by the admin via the admin site. This is to allow users to have the option of selecting the desired template for their resume. Hence, the main idea behind a CV/Resume builder.

class ResumeTemplate(models.Model):

    '''
    This is used as the template options for Resume model.
    '''
    name = models.CharField(max_length=300)
    template_image = models.ImageField(upload_to='resume_samples/')

    def __str__(self):
        return self.name

class Resume(models.Model):
....

upload_to field in ImageField answers the question "Where do you want the files that will be uploaded in the field to be stored?" ImageField requires Pillow, a python package, to be installed to utilize the ImageField model data type. Run the following command:

>> pip install pillow

We run migrations to see how far we've gone and try interacting with the models from our admin site.

>> python manage.py makemigrations

The above command creates a migration file in cveate/cv/migrations/ folder. If you observe from the admin site, no changes have been seen. Now run:

>> python manage.py migrate

Visting localhost:8000/admin after providing your log in details, you will still see no difference. This is because you have not registered it on the admin site. To register, navigate to cveate/cv/admin.py

from django.contrib import admin
from cv import models   # new

# Register your models here.
admin.site.register(models.ResumeTemplate)  # new
admin.site.register(models.Resume)  # new

Now, visit the admin site. Your dashboard should look similar to this:

register model in admin site.png

Click on Resume Templates on the dashboard and create a new resume template named "casual". After creation, your Resume Template list should display this:

resume_template_create-admin.png Add a resume that belongs to the admin (your account) via the admin dashboard. Upon creation, your Resume list should display this:

resume_create-admin.png

Notice resume shows "Resume object(1)" whereas resume template shows the name "casual". This is because of the __str__ method included in ResumeTemplate in cveate/cv/models.py file. The method converts the model's object to a human-readable form depending on the return statement.

To make resume human readable, let's add __str__ method in our Resume model:

class Resume(models.Model):

    '''Resume model'''
    user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='resumes')
    job_title = models.CharField(max_length=200)
    first_name = models.CharField(max_length=200)
    last_name = models.CharField(max_length=200)
    email = models.EmailField()
    professional_summary = models.TextField()
    resume_template = models.ForeignKey(
        ResumeTemplate,
        on_delete=models.CASCADE
    )

    def __str__(self):   # new
        return self.first_name     # new

Refresh your admin site, you should no longer see "Resume object(1)".

Uploading Images via the Django Admin site

Upload a sample image you have on your computer to the resume template via the admin site. Mine is named "preview.jpg", .

You should see a link that holds the currently uploaded content, mine is "resume_samples/preview.jpg":

admin resume template upload_LI.jpg

Try clicking on the link, you will be redirected to the dashboard with a warning message similar to the image below;

upload error (no media root)_LI.jpg

This is a result of how Django stores uploaded content. Uploaded contents are stored in a folder in the base called Media. To set up the above, we shall add it to our cveate/cveate/settings.py and cveate/cveate/urls.py files.

In cveate/cveate/settings.py,

...
STATIC_URL = '/static/'

MEDIA_URL = '/media/'   # new
MEDIA_ROOT = os.path.join(BASE_DIR, 'media/')   # new

In cveate/cveate/urls.py,

from django.conf import settings # <--- new
from django.conf.urls.static import static # new
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('resume', include('cv.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) # new

Try clicking on the currently uploaded template file from the resume template, you should still see something similar to this error: 404-media.png

The above error simply says that the uploaded file is not currently stored in the media directory. Hence, we need to re-upload the file again to have it stored under media content.

Again, click on the currently uploaded template file for the resume template named casual. You should see a photo of the file you uploaded.

Other Models

Other models follow the same setup. Using the ER-diagram to design our models, we have the following in cveate/cv app:

What next?

This wraps up model creation for our CV/Resume builder. In the next part, we shall begin work on views, serializers, and routes๐Ÿ˜ƒ.