Django notes -- 03 implementation of recruitment evaluation system

Posted by broann on Sun, 23 Jan 2022 08:56:59 +0100

1. Product background and iterative thinking

MVP: the smallest available product, the smallest subset of functions that can make the business run

That is, first implement the most core and important functions in the current version, and then iterate the functions step by step

How to find out the MVP function scope of the product:

  • Determine the core objectives, core users and core scenarios of the product
  • Do product objectives need to be completed or presented online
  • What should the minimum MVP product do to achieve business objectives
  • Which functions are not on the core path of user process
  • What simplifications and assumptions can be made to deliver products in the shortest time and make the business process run

2. Ten principles of enterprise database design

1. Three basic principles

  • Clear structure: the table name and field name have no ambiguity and can be understood at a glance
  • Sole responsibility: one table for one use, clear domain definition, no irrelevant information is stored, and relevant data is in one table
  • Primary key responsibilities: design primary keys without physical meaning, have unique constraints, and ensure idempotence

2. 4 scalability principles (affecting system performance and capacity)

  • Separation of length and length: it can be expanded. Long text can be stored independently with appropriate capacity design. Usually, long text is stored in KT system
  • Hot and cold separation: separation of current data from historical data
  • Complete index: there is an appropriate index to facilitate query
  • Disassociated query: do not use all SQLJoin operations, and do not perform associated queries for two or more tables

3. Three completeness principles

  • Integrity: ensure the accuracy and integrity of data, and record the main contents
  • Traceable: traceable to the creation time, modification time, and can be deleted logically
  • Consistency principle: the data shall be consistent, and the same data shall not be stored in different tables as far as possible

3. In model__ unicode__ And__ str__ Use of

 #python2 gives priority to using this method to convert objects into strings, if not__ unicode__ () method, use__ str__ () method
def __unicode__(self):
	return self.username

#Python 3 direct definition__ str__ () method, which is used by the system to convert the object into a string
def __str__(self):
    return self.username  #Defines the field name that the view displays

4. Grouping of detail pages in admin

# Grouping, fieldsets is the set list of fields; The first element of the tuple represents the name of the group display; The second element is a map that defines which fields are available
fieldsets = (
    (None, {'fields': ("userid", ("username", "city"), ("phone", "email"), ("apply_position", "born_address"), ("gender", "candidate_remark"), ("bachelor_school", "master_school", "doctor_school"), ("major", "degree"), ("test_score_general_ability", "paper_score"),"last_editor",)}),
    ('First round interview record', {'fields': (("first_score", "first_learning_ability", "first_professional_competency"), "first_advantage", "first_disadvantage", ("first_result", "first_recommend_position"), ("first_interviewer", "first_remark"),)}),
    ('Second round interview record', {'fields': ("second_score", ("second_learning_ability", "second_professional_competency", "second_pursue_of_excellence"), ("second_communication_ability", "second_pressure_score"), "second_advantage", "second_disadvantage", ("second_result", "second_recommend_position"), ("second_interviewer", "second_remark"),)}),
    ('HR Retest record', {'fields': ("hr_score", ("hr_responsibility", "hr_communication_ability", "hr_logic_ability", "hr_potential", "hr_stability"), "hr_advantage", "hr_disadvantage", ("hr_result", "hr_interviewer", "hr_remark"),)})
)

5. Batch import candidate data from Excel file

Create a file named import_candidates

class Command(BaseCommand):
    help = 'From one CSV Read the candidate list from the contents of the file and import it into the database' # Help text

    #Command line parameters
    def add_arguments(self, parser):
        parser.add_argument('--path', type=str) # Use the long command line, which is commonly used in Linux commands, -- represents a long command, and the parameter passed in is a string type

    # processing logic 
    def handle(self, *args, **options):
        path = options['path'] # Read path
        with open(path,'rt',encoding='gbk') as f: # Open file as read-only
            reader = csv.reader(f,dialect='excel',delimiter=';') # Read files with csv; dialect: Specifies the format, delimiter: Specifies the delimiter
            for row in reader:  # Traverse each line
                candidate = Candidate.objects.create(
                    username = row[0],
                    city = row[1],
                    phone=row[2],
                    bachelor_school=row[3],
                    major=row[4],
                    degree=row[5],
                    test_score_general_ability=row[6],
                    paper_score=row[7]
                )
                print(candidate)

Enter the command on the terminal: Python manage py import_ candidates --path ./ candidates. csv to import csv content

The content format of csv file is:

6. List filtering and query

Operate in admin

# List page search properties
search_fields = ('username', 'phone', 'email', 'bachelor_school')

#Filter criteria (a filter block appears in the display page coordinates)
list_filter = ('city','first_result','hr_result','first_interviewer_user','second_interviewer_user','hr_interviewer_user')

# List page sorting
ordering = ('hr_result', 'second_result', 'first_result')

7. Export data to csv file

Define a function export in admin_ model_ as_ csv

# Realize data export function
exportable_fields = ('username', 'city', 'phone', 'bachelor_school', 'master_school', 'degree', 'first_result', 'first_interviewer',
                     'second_result', 'second_interviewer', 'hr_result', 'hr_score', 'hr_remark', 'hr_interviewer')


# Request is the request initiated by the user, and queryset is the data set in the result list selected by the user on the interface
def export_model_as_csv(modeladmin,request,queryset): # Request is the request initiated by the user. queryset is the data set in the result list selected by the user in the list
    response = HttpResponse(content_type='text/csv')
    field_list = exportable_fields  # Exported fields
    # Specifies the format of the exported file
    response['Content-Disposition'] = 'attachment; filename=%s-list-%s.csv' %(
        'recruitment-candidates',
        datetime.now().strftime('%Y-%m-%d-%H-%M-%S')
    )

    #Write header
    writer = csv.writer(response)
    writer.writerow(
        # Take the Chinese name displayed on the page corresponding to each field as the header in our export file,
        [queryset.model._meta.get_field(f).verbose_name.title() for f in field_list]
    )

    # Write in every line of data
    for obj in queryset:
        #A single line record (the value of each field) is written to the csv file
        csv_line_values = []
        for field in field_list:
            field_object = queryset.model._meta.get_field(field)
            field_value = field_object.value_from_object(obj)
            csv_line_values.append(field_value)
        writer.writerow(csv_line_values)
    return response

# Define the properties of this function
export_model_as_csv.short_description = u'Export as CSV file'

Register the function in actions, and the actions will be registered in the function that inherits ModelAdmin

actions = [export_model_as_csv,]

8. Record logs to facilitate troubleshooting


Four components

  • Loggers: processing class / object of logging. A Logger can have multiple Handlers
  • Handler: how to handle each log message, record it to file, console or network
  • Filters: defines filters, which are used on the Logger/Handler
  • Formatters: defines the format of log text records

Four log levels

  • DEBUG: DEBUG
  • INFO: common system information
  • WARNING: a small WARNING that does not affect the main functions
  • ERROR: the system has an ERROR that cannot be ignored
  • CRITICAL: a very serious error
# Logging
LOGGING = {
    "version": 1,   # Version number of the logging format
    "disable_existing_loggers":False, # Disable the existing Logger
    
    "handlers": {
        "console": {
            "class": "logging.StreamHandler",  
        },
    },
    "loggers":{
        "django_python3_ldap":{
            "handlers":["console"], # It will output to the console
            "level":"DEBUG", # level
        },
    },
}

Detailed definition

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'formatters': {
        'simple': { # exact format is not important, this is the minimum information
            'format': '%(asctime)s %(name)-12s %(lineno)d %(levelname)-8s %(message)s',
        },
    },
    'handlers': {
        'console': {
            'class': 'logging.StreamHandler',  # Output to console
            'formatter': 'simple',
        },

        'mail_admins': { # Add Handler for mail_admins for `warning` and above
            'level': 'ERROR',  # Defines the level of error. The log is sent to the mail and sent to the mail processor
            'class': 'django.utils.log.AdminEmailHandler',
        },
        'file': {    # Record to file and record log information to file
            #'level': 'INFO',
            'class': 'logging.FileHandler',
            'formatter': 'simple',
            'filename': os.path.join(BASE_DIR, 'recruitment.admin.log'),  #BASE_ The previous directory of dir, that is, under the previous directory of the project directory
        },

        'performance': {
            #'level': 'INFO',
            'class': 'logging.FileHandler',
            'formatter': 'simple',
            'filename': os.path.join(BASE_DIR, 'recruitment.performance.log'),
        },
    },

    'root': {   #The default Logger at the global level of the system is a special Logger in Logger
        'handlers': ['console', 'file'],  # Console and file output at the same time
        'level': 'INFO',
    },
        "interview.performance": {
            "handlers": ["console", "performance"],
            "level": "INFO",
            "propagate": False,
        },
    }

Custom log

Define Logger class

loggng=logging.getLogger(__name__)

Write in the function that needs to be written to the log. Take the function of exporting csv file as an example:

logging.info('%s exported %s candidate records' % (request.user, len(queryset)))

9. Separation of production environment and development environment configuration

Question:

  • The configuration of the production environment is separated from that of the development environment, which allows Debugging
  • Sensitive information is not submitted to the code base, such as database connection, secret key, LDAP connection information, etc
  • The configurations used in production and development environments may be different. For example, MySQL/Sqlite databases are used respectively

This information can be put into the configuration of the development environment, and the configuration of the development environment can be changed from Go inside gitignore and specify it

You can create a settings package in the project directory and set settings Put the configuration of Py in the settings package and rename the file base Py, you also need to create one in the settings package__ init__.py file

The original reference configuration was in manage Py, manage Py has a setdefault, which sets DJANGO_SETTINGS_MODULE refers to the configuration of settings under the recruitment package, so it should be changed to base under the settings package py

When we start the django environment and do not specify the django configuration by default, we use settings base

In settings Base contains the basic configuration and creates local Py and production Py configuration file, you can set local Py condition reached In gitignore

Specify configuration:

python manage.py runserver 0.0.0.0:8000 --settings=settings.local

The specified configuration will overwrite the configuration in the base

10. Perfect page

Title, defined in urls:

from django.utils.translation import gettext as _
admin.site.site_header = _('Jiangguo technology recruitment management system')

Use help in the model field_ Text to set the prompt content

HR decides who the interviewer is. The interviewer cannot change HR's decision

Change interviewer to option:

second_interviewer_user = models.ForeignKey(User, related_name='second_interviewer_user', blank=True,  #It refers to a foreign key, so it becomes choice
                                               null=True, on_delete=models.CASCADE,
                                               verbose_name=u'interviewer')

For the interviewer, the interviewer field can only be read:

#The first approach
#Set the read-only field and the detailed page. This is a method facing all users
readonly_fields = ('first_interviewer_user','second_interviewer_user')
# The second method
#  Set the read-only field, detailed page, the second method
def get_group_names(self,user):
    group_names = []
    for g in user.groups.all():
        group_names.append(g.name)
    return group_names
    
def get_readonly_fields(self, request, obj=None):
    group_names = self.get_group_names(request.user) # Get the user's group
    if 'interviewer' in group_names:
        logging.info("interviewer is in user's group for %s" % request.user.username)
        return ('first_interviewer_user','second_interviewer_user')
    return ()

Enables HR to make batch modifications on the list page

# The first way is for all users
#superuser modifiable options for data form pages
list_editable = ('first_interviewer_user','second_interviewer_user')
# The second way
def get_list_editable(self, request):
    group_names = self.get_group_names(request.user)

    if request.user.is_superuser or 'hr' in group_names:
        return ('first_interviewer_user','second_interviewer_user',)
    return ()

# Use get_list_editable returns a list. django does not recognize it because django does not recognize the list_editable has no corresponding function to support the setting of this property, so it overrides a get defined in ModelAdmin_ changelist_ Instance, overriding the parent class list_ Properties of editable
def get_changelist_instance(self, request):  #Override list of parent class_ Editable property
    self.list_editable = self.get_list_editable(request)
    return super(CandidateAdmin, self).get_changelist_instance(request)