Skip to content

Latest commit

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..

readme.md

სტრუქტურიზაცია

წინა რამოდენიმე თავში უკვე წავაწყდით ერთ ფაილში დიდი ზომის კოდის დაგროვების პრობლემას, რაც სამუშაო პროცესს და კოდის აღქმის პროცესს ართულებს. რაც უფრო იზრდება პროექტი ზომაში, მით უფრო ძლიერ იჩენს ეს პრობლემა თავს. შესაბამისად, პროექტის დაწყებიდან ვიფიქროთ პროგრამის პრაქტიკულ სტრუქტურიზაციასა და კოდის ოპტიმიზაციაზე. წინა თავებში გავეცანით რამოდენიმე მეთოდს, დღეს კი ამ ყველაფერს დავამატებთ flask აპლიკაციის სტრუქტურიზაციის ერთ-ერთ ფორმას (დაფუძნებულია flask-ის მიერ შემოთავაზებულ პროექტის სტრუქტურაზე) და გავერთიანებთ ამ ცოდნას ერთ პროექტში.

სარჩევი

[TOC]

შესავალი

წინა თავში ძალიან ბევრი რამ მოგვიგროვდა ძირითად app/portal.py-ში. მიუხედავად იმისა რომ ფორმების გენერატორი ცალკე form.py ფაილში გავიტანეთ, რაც სტრუქტურიზაციის ძალიან კარგი პრაქტიკაა, მთავარ ფაილში კიდევ ბევრი რამ დაგვრჩა დასაბინავებელი.

როგორც შევთანხმდით, app.py-ში უნდა მოთავსდეს სერვერული აპლიკაციის გასაშვებად საჭირო კოდი, ბიბლიოთეკები, კონფიგურაცია, რესურსებთან ბმა. კოდის სხვა დანარჩენი ნაწილი უნდა მოვათავსოთ ოპტიმალურ ფაილებში და გავუკეთოთ ბმა ცენტრალურ app.py-სთან. ამისთვის აუცილებელია პროგრამა დავყოთ ფუნქციონალურ პორციებად და განავათავსოთ შესაბამის ფაილებში. მაგალითად: მოდელები => models.py ; გვერდი viewები => views.py-ში და ა.შ.

შაბლონი

სტრუქტურიზაციის პროცესი გვეხმარება დეველოპმენტ პროცესის კომფორტულად წამართვაში. აუცილებელია პროექტს გავუკეთოთ ისეთი სტრუქტურა, რომ როგორც ერთი დეველოპერის ისე დეველოპერთა გუნდისთვისაც მარტივი აღსაქმელი და მისახვედრი იყოს, სად რა არის მოთავსებული.

ამისთვის პროექტი უნდა დავყოთ შემადგენელ ძირითად ელემენტებად და ამ ელემენტების სამუშაოდ საჭირო კომპონენტებად (რომელიც თავის თავში შეიძლება მოიცავდეს ქვერესურსებს). შესაბამისად ვიღებთ ხისებრ სტრუქტურას სადაც ყველაფერი უკავშირდება ძირითად სერვერულ აპლიკაციას.

მაგალითისთვის განვიხილოთ წინა თავისთვის შერჩეული პრეოქტი და ვეცადოთ მის სტრუქტურიზებას შაბლონის მიხედვით. გაითვალისწინეთ რომ ეს არ არის მკაცრად განსაზღვრული წესი თუ როგორი სტრუქტურა უნდა ჰქონდეს თქვენს პროექტს, არამედ უფრო შემოთავაზება თუ როგორ მოაწყოთ ოპტიმალური გარემო.

შესაბამისად წინა თავში განხილული სტუდენტ-მასწავლებლის პროექტის სტრუქტურიზაცია ასეა შესაძლებელი:

📦StudentPortal
├───app.py # მთავარი app.py რომლის გამოძახებითაც გაეშვება web app სერვერი
├───requirements.txt # პროექტისთვის საჭირო ბიბლიოთეკების სია pip install-თ დასაყენებლად
├───📂migrations # ფოლდერი შექმნილი მიგრაციებისთვის
├───📂myproject # პროექტის რესურსების მთავარი ფოლდერები, კომპონენტები გადანაწილებულია სუბ-ფოლდერებში
│ │ __init__.py # აპლიკაციის აღწერა და კონფიგურაცია
│ │ │ ├───📂static # ადგილი სტატიკური ფაილებისთვის: CSS, JS, Images, Fonts, etc...
│ ├───📂templates # ვებ აპლიკაციის ძირითადი/სასტარტო შაბლონები
│ │ │ base.html
│ │ │ home.html
│ │ # კომპონენტების საზიარო რესურსები
│ │ data.sqlite # ლოკალური ბაზა
│ │ models.py # ბაზის მოდელები
│ │ # კომპონენტები
│ ├───📂teachers
│ │ │ forms.py
│ │ │ views.py
│ │ │
│ │ ├───📂templates
│ │ │ └───📂teachers
│ │ │ │ add_teachers.html
... ...
... ...
│ ├───📂students
│ │ │ forms.py
│ │ │ views.py
│ │ │
│ │ ├───📂templates
│ │ │ └───📂students │ │ │ │ add.html
│ │ │ │ delete.html
│ │ │ │ list.html

რაც უფრო იზრდება პროექტი მით უფრო იზრდება აპლიკაციის სამუშაო კოდის ზომა. როგორ ხედავთ გარდა იმისა რომ გავმიჯნეთ ერთმანეთისგან სერვერის დასასტარტად და პროექტის სამუშაოდ საჭირო რესურსები, მოვათავსეთ ეს რესურსები საერთოmyproject დირექტორიაში. შემდგომ ამ დირექტორიაში რესურსებს გავუკეთეთ ცალკე კატეგორიზაცია, მაგალითად ცალკე მოვათავსეთ შაბლონები, ცალკე ბაზის მოდელები და ქვე დირექტორიებად გავიტანეთ სამუშაო ობიექტები - კომპონენტები. პრაქტიკულია თუ პროექტს ასე დავყოფთ შემადგენელ კომპონენტებად - იმ ობიექტებად, რომლის გარშემოც შესაძლებელია რესურსების დაჯგუფება და სტრუქტურიზაცია.

ჩვენი შემთხვევისთვის გვაქვს მსგავსი ორი კომპონენტი: students - მოსწავლეები და teachers - მასწავლებლები. მათ გააჩნიათ ქვეკომპონენტები: ფორმები და ვიუები და ქვერესურსები - შაბლონები.

გადანაწილება

მას შემდეგ რაც თეორიულად მიმოვიხილეთ თუ როგორი სტრუქტურა უნდა ჰქონდეს პროექტს, გადავანაწილოთ და დავსვათ წინა თავში აწყობილი პროექტი შესაბამის სტრუქტურაზე. ამისთვის ძირითადად ახალი დირექტორიების შექმნა და მათში წინა თავიდან copy / paste გადმოტანილი ფაილების ოპტიმიზაცია დაგვჭირდება. (რადგან კოდის ძირითადი ნაწილი უკვე დავწერეთ წინა თავში)

პროცესის გამარტივებაში დაგვეხმარება flask-ში ჩაშენებული blueprints შესაძლებლობები, რომლითაც გავწერთ მოდულარ კომპნენტებს, მაგალითად დავაკავშირებთ მათ შესაბამის ვიუებთან.

მაგალითისთვის, ჩვენ გვექნება ორი views.py ფაილი, ერთი მასწავლებლების კომპონენტისთვის ხოლო მეორე სტუდენტების. თითოეულ ამ ფაილს ექნება თავისი add view. იმისთვის რომ დავეხმაროთ flaskს განასხვავოს /add მარშუტები, ჩვენ გამოვიყენებთ ბლუპრინტს.

ბლუპრინტის მეშვეობით გავწერთ მისამართის პრეფიქსებს თითოეული view ფაილისთვის. მაგალითად:

/students/add და /teachers/add

შესაბამისად დღევანდელი ჩვენი სამუშაოა:

  • არსებული კოდის რესტრუქტურიზაცია და შესაბამის ფოლდერებში დაბინავება
  • პროექტის ახალი სტრუქტურის შესაბამისად კოდის გამართვა
  • პროექტში ბლუპრინტის ჩამატება
  • ბლუპრინტის გაწერა __init__.py ფაილში.

შაბლონის მიხედვით შევქმნათ ფოლდერები და ფაილები. შემდეგ ქვე-თავში ვნახავთ როგორ უნდა გავმართოთ კოდი ამ სტრუქტურის მიხედვით:

კოდის გამართვა

ინდივიდუალურად გაგიზიარებთ თითოული ფაილის შიგთავს კოდს თქვენ კი მოარგეთ კოდები თქვენს პროექტს.

app.py

frommyprojectimportappfromflaskimportrender_template@app.route('/')defindex():
returnrender_template('home.html')
if__name__=='__main__':
app.run(debug=True)

/myproject

models.py

რადგანაც სულ ორი მოდელი გვაქვს პროექტში, გადავწყვიტე მათი ერთ ფაილში დატოვება, რადგან კოდის ზედმეტად დანაწევრებაც არ არის საჭირო. თუმცა თუ თქვენი მოდელების ზომა და მათში შემავალი მეთოდები დიდია რა საკვირველია სასურველია მათი სტრუქტურიზაციაც. ჩემს შემთხვევაში ფაილი ასე გამოიყურება:

frommyprojectimportdbclassStudent(db.Model):
__tablename__="students"id=db.Column(db.Integer, primary_key=True)
name=db.Column(db.String)
# One_to_one relationship# A student only has one teacher, thus uselist is False.# Strong assumption of 1 teacher per 1 student and vice versa.teacher=db.relationship('Teacher', backref="student", uselist=False)
def__init__(self, name):
# მხოლოდ გვჭირდება ამ ბაზის მოდელისთვის უნიკალური წევრის ატრიბუტის აღწერაself.name=namedef__repr__(self):
ifself.teacher:
returnf"Teacher of the Student {self.name} is {self.teacher.name}"else:
returnf"Student {self.name} has no teacher yet"classTeacher(db.Model):
id=db.Column(db.Integer, primary_key=True)
# Connect the teacher to the Student that "owns" it.# We use student.id because __tablename__='student'student_id=db.Column(db.Integer, db.ForeignKey('students.id'))
name=db.Column(db.String)
def__init__(self, name, student_id):
self.name=nameself.student_id=student_iddef__repr__(self):
returnf"Teacher Name: {self.name}"

ერთადერთი განსხვავება რაც ძველი კოდისგან გვაქვს არის from myproject import db. ეს იმიტომ რომ ჩვენ სერვერული პროგრამის აღწერას (შესაბამისად ფლასკის კონფიგურაციის და მონაცემთა ბასიზაც) გადავიტანთ myproject/__init__.py-ფაილში.

__init__.py

importosfromflaskimportFlaskfromflask_sqlalchemyimportSQLAlchemyfromflask_migrateimportMigrateapp=Flask(__name__)
# თუ კონფიგურაციისათვის იყენებთ ბევრ პარამეტრს სასურველია მათი config.py ფაილში გადანაწილება app.config['SECRET_KEY'] ='mysecretkey'basedir=os.path.abspath(os.path.dirname(__file__))
app.config['SQLALCHEMY_DATABASE_URI'] ='sqlite:///'+os.path.join(basedir, 'data.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] =Falsedb=SQLAlchemy(app)
Migrate(app,db)
# გაითვალისწინე! აუცილებელია იმპორტების გაკეთება მას შემდეგ რაც db ობიექტს გაწერ# წინააღმდეგ შემთხვევაში models.py დაგვიერორდება.## ბაზასთან მომუშავე views.py ფაილები სათითაოდ შემოვიტანოთ მათი ბლუპრინტებითfrommyproject.students.viewsimportstudents_blueprintfrommyproject.teachers.viewsimportteachers_blueprint# ამის შემდგომ უკვე შეგვიძლია ბლუპრინტების რეგისტრირებაapp.register_blueprint(students_blueprint,url_prefix="/students")
app.register_blueprint(teachers_blueprint,url_prefix='/teachers')

ბლუპრინტებმა არ დაგაბნიოთ ამ ეტაპზე. როდესაც ვიუებს გავწერთ იქ ვისაუბრებთ თუ რას აკეთებენ ამ ეტაპისთვის უხილავ ნაწილში.

ამ საფეხურისთვის გი გაიხსენეთ როგორ ვამატებთი api-ს წინასწარ გაწერილ ობიექტს. აქაც მსგავსი სტრუქტურა გვაქვს. პროექტში შემოგვაქვს ვიუები რომელიც მოთავსებული შესაბამისი ვიუს ბლუპრინტში (მაგ. students_blueprint) და register_blueprint() მეთოდის გამოყენებით, ჩვენს აპლიკაციაში ვარეგისტრირებთ ვიუ რესურსს შესაბამის მისამართზე.

და როგორც API-ს შემთხვევაში, ნებისმიერი ფუნქციონალი რომელიც რესურსში იყო გაწერილი ავტომატურად აქტიურდებოდა შესაბამის მისამართსა თუ მეთოდის ტიპზე, ანალოგიურად გააქტიურდება ვიუში გაწერილი ფუნქციონალები, ბლუპრინტის მიხედვით.

/templates

base.html

<!DOCTYPE html><htmllang="en"><head><metacharset="UTF-8"><title>სტუდენტის პორტალი</title><!-- CSS only --><linkrel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" integrity="sha384-JcKb8q3iqJ61gNV9KGb8thSsNjpSL0n8PARn9HuZOnIxN0hoP+VmmDGMN5t9UJ0Z" crossorigin="anonymous"><!-- JS, Popper.js, and jQuery --><scriptsrc="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script><scriptsrc="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script><scriptsrc="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js" integrity="sha384-B4gt1jrGC7Jh4AgTPSdUtOBvfO8shuf57BaghqFfPlYxofvL8/KUEfYiJOMMV+rV" crossorigin="anonymous"></script></head><body><navclass="navbar navbar-expand-lg navbar-light bg-light"><divclass="navbar-nav"><aclass="nav-item nav-link" href="{{ url_for('home') }}">თავფურცელი</a><aclass="nav-item nav-link" href="{{ url_for('student.add') }}">სტუდენტის დამატება</a><aclass="nav-item nav-link" href="{{ url_for('student.list') }}">სტუდენტის სია</a><aclass="nav-item nav-link" href="{{ url_for('student.delete') }}">სტუდენტის წაშლა</a><aclass="nav-item nav-link" href="{{ url_for('teacher.add') }}">მასწავლებლის დამატება</a></div></nav>
{% block content %}
{% endblock %}
</body></html>

home.html

{%extends"base.html"%}{%blockcontent%}
<divclass="jumbotron">
<h1>მოგესალმებით სტუდენტის პლატფორმაზე</h1>
<p>გთხოვთ აირჩიოთ შესაბიმისი ფუნქციონალი სანავიგაციო ველიდან</p>
</div>
{%endblock%}

როგორც ხედავთ home.html წინა თავის იდენტურია. აქ შეგიძლიათ ნებისმიერი კონტენტი მოათავსოთ რისი გამოჩენაც თავფურცელზე გსურთ.

components

myproject//forms.py

ორივე კომპონენტის ფორმების ფაილს ერთ ქვეთავში გავაერთიანებ რომ ამ ინსტრუქციის მიყოლა გაგიმარტივდეთ. ამ ეტაპისთვის შაბლონის მიხედვით უნდა გქონდეთ შექმნილი თითოეული კომპონენტის დირექტორია შესაბამისი forms.py ფაილებით:

myproject/students/forms.py

fromflask_wtfimportFlaskFormfromwtformsimportStringField, IntegerField, SubmitFieldclassAddForm(FlaskForm):
name=StringField('სტუდენტის სახელი:')
submit=SubmitField('სტუდენტის დამატება')
classDelForm(FlaskForm):
id=IntegerField('სტუდენტის უნიკალური იდენტიფიკატორი:')
submit=SubmitField('სტუდენტის წაშლა')

myproject/teachers/forms.py

fromflask_wtfimportFlaskFormfromwtformsimportStringField, IntegerField, SubmitFieldclassAddForm(FlaskForm):
name=StringField('მასწავლებლის სახელი:')
student_id=IntegerField("სტუდენტის იდენტიფიკატორი: ")
submit=SubmitField('მასწავლებლის დამატება')

myproject//views.py

მივადექით თავის ყველაზე საინტერესო ნაწილს, view ფაილებს. სწორედ ამ ფაილებში დავამატებთ ბლუპრინტებს. ეს სამუშაო შემდეგ საფეხურებად შეგვიძლია ჩავშალოთ:

  1. ავაწყოთ view ფაილი რომელიც შაბლონის და ფუნქციონალის დაკავშირებაზეა პასუხისმგებელი
  2. ჩავამატოთ Blueprints-ები
  3. დავარეგისტრიროთ Blueprint-ები
  4. დავუკავშიროთ app.py-ს
  5. ჩავამატოთ მისამართები base.html-ის navbar-ში

პროცესი უნდა დავიწყოთ ბლუპრინტის და სხვა საჭირო რესურსების შემოტანით:

# ფლასკ აპლიკაციის ხელსაწყოების შემოტანაfromflaskimportBlueprint, render_template, redirect, url_for# შაბლონში ბაზასთან სამუშაო ფუნქციონალის ჩასაშენებლადfrommyprojectimportdb# ასევე დაგვჭირდება მოდელი, რომლის მიხედვითაც ვაწყობთ ამ ფუნქციონალსfrommyproject.modelsimport<ModelName># და საბოლოოდ ფორმები რომელიც გვინდა ვებ გვერდში გამოვიყენოთfrommyproject.<component>.formsimport<FormFields>

ამის შემდგომ დაგვჭირდება კომპონენტის Blueprints ობიექტის შექმნა:

component_blueprint=Blueprint('<component_name>', __name__, template_folder='templates/<component_name>')

საბოლოოდ რჩება ფუნქციონალის მისამართზე მიმაგრება.

routing-ში განსხვავება ისაა რომ app ის მაგივრად ახალ მისამართს ჩვენს component_blueprint ობიექტზე ვამატებთ. შესაბამისად მარშუტის სტრუქტურა ასეთი იქნება:

@component_blueprint.route('/add', methods=['GET', 'POST'])

სწორედ ამ ობიექტებს დავლინკავთ app.py-სთან მთელი თავისი რესურსით/მეთოდებით. შესაბამისად მივიღებთ ორ ფაილს:

myproject/students/views.py

fromflaskimportBlueprint,render_template,redirect,url_forfrommyprojectimportdbfrommyproject.students.formsimportAddForm,DelFormfrommyproject.modelsimportStudentstudents_blueprint=Blueprint('students',
__name__,
template_folder='templates/students')
@students_blueprint.route('/add', methods=['GET', 'POST'])defadd():
form=AddForm()
ifform.validate_on_submit():
name=form.name.data# Add new Student to databasenew_student=Student(name)
db.session.add(new_student)
db.session.commit()
returnredirect(url_for('students.list'))
returnrender_template('add.html',form=form)
@students_blueprint.route('/list')deflist():
# Grab a list of students from database.students=Student.query.all()
returnrender_template('list.html', students=students)
@students_blueprint.route('/delete', methods=['GET', 'POST'])defdelete():
form=DelForm()
ifform.validate_on_submit():
id=form.id.datastudent=Student.query.get(id)
db.session.delete(student)
db.session.commit()
returnredirect(url_for('students.list'))
returnrender_template('delete.html',form=form)

myproject/teachers/views.py

fromflaskimportBlueprint,render_template,redirect,url_forfrommyprojectimportdbfrommyproject.modelsimportTeacherfrommyproject.teachers.formsimportAddFormteachers_blueprint=Blueprint('teachers',
__name__,
template_folder='templates/teachers')
@teachers_blueprint.route('/add', methods=['GET', 'POST'])defadd():
form=AddForm()
ifform.validate_on_submit():
name=form.name.datastudent_id=form.student_id.data# Add new teacher to databasenew_teacher=Teacher(name,student_id)
db.session.add(new_teacher)
db.session.commit()
returnredirect(url_for('students.list'))
returnrender_template('add_teachers.html',form=form)

myproject//templates/

შაბლონებში შეგვიძლია მოვათავსოთ ნებისმიერი ის html შაბლონი რომელიც ამ კომპონენტთან სამუშოდ დაგვჭირდება. წინა მაგალითიდან გადმოვიტანოთ სტუდენტ კომპონენტთან სამუშაო შაბლონები.

students/templates/students/

add.html

{% extends "base.html" %}
{% block content %}
<divclass="jumbotron"><h1>სტუდენტის დამატება</h1><p>სტუდენტის დამატება ხდება შემდეგი ფორმიდან:</p><formmethod="POST">
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name() }}
{{ form.submit() }}
</form></div>
{% endblock %}

delete.html

{% extends "base.html" %}
{% block content %}
<divclass="jumbotron"><h1>გსურთ სტუდენტის წაშლა?</h1><p>შეავსეთ ფორმა სტუდენტის იდენტიფიკატორით</p><formmethod="POST">
{# This hidden_tag is a CSRF security feature. #}
{{ form.hidden_tag() }}
{{ form.id.label }} {{ form.id() }}
{{ form.submit() }}
</form></div>
{% endblock %}

list.html

{% extends "base.html" %}
{% block content %}
<divclass="jumbotron"><h1>იხილეთ ბაზაში დარეგისტრირებულ სტუდენტთა სია</h1><ol>
{% for student in students %}
<ul>{{ student.id }} {{ student }}</ul>
{% endfor %}
</ol></div>
{% endblock %}

/teachers/templates/teachers

add.html

{% extends "base.html" %}
{% block content %}
<divclass="jumbotron"><h1>მასწავლებლის დამატება</h1><p>მოსწავლის დამატება შეგიძლიათ ფორმიდან:</p><formmethod="POST">
{# This hidden_tag is a CSRF security feature. #}
{{ form.hidden_tag() }}
{{ form.name.label }} {{ form.name() }}<br>
{{ form.student_id.label }} {{ form.student_id() }}<br>
{{ form.submit() }}
</form></div>
{% endblock %}

დამატებითი რესურსები: