mirror of
https://github.com/liberatedsystems/openCom-Companion.git
synced 2024-12-01 17:40:36 +01:00
1066 lines
33 KiB
Python
1066 lines
33 KiB
Python
|
"""
|
|||
|
Script creates a project with the MVC pattern
|
|||
|
=============================================
|
|||
|
|
|||
|
.. versionadded:: 1.0.0
|
|||
|
|
|||
|
.. seealso::
|
|||
|
|
|||
|
`MVC pattern <https://en.wikipedia.org/wiki/Model–view–controller>`_
|
|||
|
|
|||
|
.. rubric:: Use a clean architecture for your applications.
|
|||
|
|
|||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/preview-mvc.png
|
|||
|
:align: center
|
|||
|
|
|||
|
Use a clean architecture for your applications. KivyMD allows you to quickly
|
|||
|
create a project template with the MVC pattern. So far, this is the only
|
|||
|
pattern that this utility offers. You can also include database support in
|
|||
|
your project. At the moment, support for the Firebase database
|
|||
|
(the basic implementation of the real time database) and RestDB
|
|||
|
(the full implementation) is available.
|
|||
|
|
|||
|
Project creation
|
|||
|
----------------
|
|||
|
|
|||
|
Template command::
|
|||
|
|
|||
|
python -m kivymd.tools.patterns.create_project \\
|
|||
|
name_pattern \\
|
|||
|
path_to_project \\
|
|||
|
name_project \\
|
|||
|
python_version \\
|
|||
|
kivy_version
|
|||
|
|
|||
|
Example command::
|
|||
|
|
|||
|
python -m kivymd.tools.patterns.create_project \\
|
|||
|
MVC \\
|
|||
|
/Users/macbookair/Projects \\
|
|||
|
MyMVCProject \\
|
|||
|
python3.10 \\
|
|||
|
2.1.0
|
|||
|
|
|||
|
This command will by default create a project with an MVC pattern.
|
|||
|
Also, the project will create a virtual environment with Python 3.10,
|
|||
|
Kivy version 2.1.0 and KivyMD master version.
|
|||
|
|
|||
|
.. note::
|
|||
|
Please note that the Python version you specified must be installed on your
|
|||
|
computer.
|
|||
|
|
|||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mvc-base.png
|
|||
|
:align: center
|
|||
|
|
|||
|
Creating a project using a database
|
|||
|
-----------------------------------
|
|||
|
|
|||
|
.. note::
|
|||
|
Note that in the following command, you can use one of two database names:
|
|||
|
'firebase' or 'restdb'.
|
|||
|
|
|||
|
Template command::
|
|||
|
|
|||
|
python -m kivymd.tools.patterns.create_project \\
|
|||
|
name_pattern \\
|
|||
|
path_to_project \\
|
|||
|
name_project \\
|
|||
|
python_version \\
|
|||
|
kivy_version \\
|
|||
|
--name_database
|
|||
|
|
|||
|
Example command::
|
|||
|
|
|||
|
python -m kivymd.tools.patterns.create_project \\
|
|||
|
MVC \\
|
|||
|
/Users/macbookair/Projects \\
|
|||
|
MyMVCProject \\
|
|||
|
python3.10 \\
|
|||
|
2.1.0 \\
|
|||
|
--name_database restdb
|
|||
|
|
|||
|
This command will create a project with an MVC template by default.
|
|||
|
The project will also create a virtual environment with Python 3.10,
|
|||
|
Kivy version 2.1.0, KivyMD master version and a wrapper for working with
|
|||
|
the database restdb.io.
|
|||
|
|
|||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mvc-database.png
|
|||
|
:align: center
|
|||
|
|
|||
|
.. code-block:: python
|
|||
|
|
|||
|
class DataBase:
|
|||
|
def __init__(self):
|
|||
|
database_url = "https://restdbio-5498.restdb.io"
|
|||
|
api_key = "7ce258d66f919d3a891d1166558765f0b4dbd"
|
|||
|
|
|||
|
.. note::
|
|||
|
Please note that `database.py` the shell in the `DataBase` class uses the
|
|||
|
`database_url` and `api_key` parameters on the test database (works only in read mode),
|
|||
|
so you should use your data for the database.
|
|||
|
|
|||
|
Preview of the basic MVC template using the RestDB database
|
|||
|
-----------------------------------------------------------
|
|||
|
|
|||
|
.. image:: https://github.com/HeaTTheatR/KivyMD-data/raw/master/gallery/kivymddoc/mvc-restbd-preview.png
|
|||
|
:align: center
|
|||
|
|
|||
|
Create project with hot reload
|
|||
|
------------------------------
|
|||
|
|
|||
|
Template command::
|
|||
|
|
|||
|
python -m kivymd.tools.patterns.create_project \\
|
|||
|
name_pattern \\
|
|||
|
path_to_project \\
|
|||
|
name_project \\
|
|||
|
python_version \\
|
|||
|
kivy_version \\
|
|||
|
--use_hotreload
|
|||
|
|
|||
|
Example command::
|
|||
|
|
|||
|
python -m kivymd.tools.patterns.create_project \\
|
|||
|
MVC \\
|
|||
|
/Users/macbookair/Projects \\
|
|||
|
MyMVCProject \\
|
|||
|
python3.10 \\
|
|||
|
2.1.0 \\
|
|||
|
--use_hotreload yes
|
|||
|
|
|||
|
After creating the project, open the file `main.py`, there is a lot of useful
|
|||
|
information. Also, the necessary information is in other modules of the project
|
|||
|
in the form of comments. So do not forget to look at the source files of the
|
|||
|
created project.
|
|||
|
|
|||
|
Others command line arguments
|
|||
|
======================
|
|||
|
|
|||
|
Required Arguments
|
|||
|
------------------
|
|||
|
|
|||
|
- pattern
|
|||
|
- the name of the pattern with which the project will be created
|
|||
|
|
|||
|
- directory
|
|||
|
- directory in which the project will be created
|
|||
|
|
|||
|
- name
|
|||
|
- project name
|
|||
|
|
|||
|
- python_version
|
|||
|
- the version of Python (specify as `python3.9` or `python3.8`) with
|
|||
|
- which the virtual environment will be created
|
|||
|
|
|||
|
- kivy_version
|
|||
|
- version of Kivy (specify as `2.1.0` or `master`) that will be used in the project
|
|||
|
|
|||
|
Optional arguments
|
|||
|
------------------
|
|||
|
|
|||
|
- name_screen
|
|||
|
- the name of the class which be used when creating the project pattern
|
|||
|
|
|||
|
- name_database
|
|||
|
- provides a basic template for working with the 'firebase' library
|
|||
|
- or a complete implementation for working with a database 'restdb.io'
|
|||
|
|
|||
|
- use_hotreload
|
|||
|
- creates a hot reload entry point to the application
|
|||
|
|
|||
|
-use_localization
|
|||
|
- creates application localization files
|
|||
|
|
|||
|
.. warning:: On Windows, hot reloading of Python files may not work.
|
|||
|
But, for example, there is no such problem in macOS. If you fix this,
|
|||
|
please report it to the KivyMD community.
|
|||
|
"""
|
|||
|
|
|||
|
import os
|
|||
|
import re
|
|||
|
import shutil
|
|||
|
from typing import Union
|
|||
|
|
|||
|
from kivy import Logger, platform
|
|||
|
|
|||
|
from kivymd import path as kivymd_path
|
|||
|
from kivymd.tools.argument_parser import ArgumentParserWithHelp
|
|||
|
|
|||
|
_database_model = '''import multitasking
|
|||
|
|
|||
|
from Model.base_model import BaseScreenModel
|
|||
|
|
|||
|
multitasking.set_max_threads(10)
|
|||
|
|
|||
|
|
|||
|
class {name_screen}Model(BaseScreenModel):
|
|||
|
"""
|
|||
|
Implements the logic of the
|
|||
|
:class:`~View.{name_screen}.{module_name}.{name_screen}View` class.
|
|||
|
"""
|
|||
|
|
|||
|
def __init__(self, database):
|
|||
|
self.database = database
|
|||
|
# Dict:
|
|||
|
# 'login': 'KivyMD'
|
|||
|
# 'password': '1111'
|
|||
|
self.user_data = dict()
|
|||
|
self._data_validation_status = None
|
|||
|
|
|||
|
@property
|
|||
|
def data_validation_status(self):
|
|||
|
return self._data_validation_status
|
|||
|
|
|||
|
@data_validation_status.setter
|
|||
|
def data_validation_status(self, value):
|
|||
|
self._data_validation_status = value
|
|||
|
# We notify the View -
|
|||
|
# :class:`~View.{name_screen}.{module_name}.{name_screen}View` about the
|
|||
|
# changes that have occurred in the data model.
|
|||
|
self.notify_observers({notify_name_screen})
|
|||
|
|
|||
|
@multitasking.task
|
|||
|
def check_data(self):
|
|||
|
"""
|
|||
|
Get data from the database and compares this data with the data entered
|
|||
|
by the user. This method is completely asynchronous.
|
|||
|
It does not return any value.
|
|||
|
"""
|
|||
|
|
|||
|
data = self.database.get_data_from_collection(self.database.USER_DATA)
|
|||
|
data_validation_status = False
|
|||
|
|
|||
|
if data:
|
|||
|
if self.database.name == "RestDB":
|
|||
|
data = data[0]
|
|||
|
else:
|
|||
|
data = list(data.values())[0]
|
|||
|
if (
|
|||
|
data["login"] == self.user_data["login"]
|
|||
|
and data["password"] == self.user_data["password"]
|
|||
|
):
|
|||
|
data_validation_status = True
|
|||
|
self.data_validation_status = data_validation_status
|
|||
|
|
|||
|
def set_user_data(self, key: str, value: str) -> None:
|
|||
|
"""Sets a dictionary of data that the user enters."""
|
|||
|
|
|||
|
self.user_data[key] = value
|
|||
|
|
|||
|
def reset_data_validation_status(self) -> None:
|
|||
|
self.data_validation_status = None
|
|||
|
'''
|
|||
|
|
|||
|
_without_database_model = '''from Model.base_model import BaseScreenModel
|
|||
|
|
|||
|
|
|||
|
class {name_screen}Model(BaseScreenModel):
|
|||
|
"""
|
|||
|
Implements the logic of the
|
|||
|
:class:`~View.{module_name}.{name_screen}.{name_screen}View` class.
|
|||
|
"""'''
|
|||
|
|
|||
|
_database_controller = '''
|
|||
|
{import_module}
|
|||
|
|
|||
|
|
|||
|
class {name_screen}Controller:
|
|||
|
"""
|
|||
|
The `{name_screen}Controller` class represents a controller implementation.
|
|||
|
Coordinates work of the view with the model.
|
|||
|
The controller implements the strategy pattern. The controller connects to
|
|||
|
the view to control its actions.
|
|||
|
"""
|
|||
|
|
|||
|
def __init__(self, model):
|
|||
|
self.model = model # Model.{module_name}.{name_screen}Model object
|
|||
|
self.view = {name_view}(
|
|||
|
controller=self, model=self.model
|
|||
|
)
|
|||
|
|
|||
|
def set_user_data(self, key: str, value: str) -> None:
|
|||
|
"""Called every time the user enters text into the text fields."""
|
|||
|
|
|||
|
self.model.set_user_data(key, value)
|
|||
|
|
|||
|
def on_tap_button_login(self) -> None:
|
|||
|
"""Called when the `LOGIN` button is pressed."""
|
|||
|
|
|||
|
self.view.show_dialog_wait()
|
|||
|
self.model.check_data()
|
|||
|
|
|||
|
def reset_data_validation_status(self, *args) -> None:
|
|||
|
self.model.reset_data_validation_status()
|
|||
|
'''
|
|||
|
|
|||
|
_without_database_controller = '''
|
|||
|
{import_module}
|
|||
|
|
|||
|
|
|||
|
class {name_screen}Controller:
|
|||
|
"""
|
|||
|
The `{name_screen}Controller` class represents a controller implementation.
|
|||
|
Coordinates work of the view with the model.
|
|||
|
The controller implements the strategy pattern. The controller connects to
|
|||
|
the view to control its actions.
|
|||
|
"""
|
|||
|
|
|||
|
def __init__(self, model):
|
|||
|
self.model = model # Model.{module_name}.{name_screen}Model
|
|||
|
self.view = {name_view}(controller=self, model=self.model)
|
|||
|
|
|||
|
def on_tap_button_login(self) -> None:
|
|||
|
"""Called when the `LOGIN` button is pressed."""
|
|||
|
|
|||
|
def set_user_data(self, key, value) -> None:
|
|||
|
"""Called every time the user enters text into the text fields."""
|
|||
|
'''
|
|||
|
|
|||
|
_database_view_import = """from typing import Union
|
|||
|
|
|||
|
from kivy.clock import Clock
|
|||
|
|
|||
|
from kivymd.uix.dialog import MDDialog
|
|||
|
from kivymd.uix.snackbar import Snackbar
|
|||
|
"""
|
|||
|
|
|||
|
_without_database_view_import = """
|
|||
|
"""
|
|||
|
|
|||
|
_database_view_methods = ''' def __init__(self, **kwargs):
|
|||
|
super().__init__(**kwargs)
|
|||
|
self.dialog = MDDialog()
|
|||
|
self.dialog.bind(on_dismiss=self.controller.reset_data_validation_status)
|
|||
|
|
|||
|
def show_dialog_wait(self) -> None:
|
|||
|
"""Displays a wait dialog while the model is processing data."""
|
|||
|
|
|||
|
self.dialog.auto_dismiss = False
|
|||
|
self.dialog.text = "Data validation..."
|
|||
|
self.dialog.open()
|
|||
|
|
|||
|
def show_toast(self, interval: Union[int, float]) -> None:
|
|||
|
Snackbar(
|
|||
|
text="You have passed the verification successfully!",
|
|||
|
snackbar_x="10dp",
|
|||
|
snackbar_y="10dp",
|
|||
|
size_hint_x=.8,
|
|||
|
bg_color=self.theme_cls.primary_color,
|
|||
|
).open()
|
|||
|
'''
|
|||
|
|
|||
|
_database_view_model_is_changed_method = """if self.model.data_validation_status:
|
|||
|
self.dialog.dismiss()
|
|||
|
Clock.schedule_once(self.show_toast, 1)
|
|||
|
if self.model.data_validation_status is False:
|
|||
|
self.dialog.text = "Wrong data!"
|
|||
|
self.dialog.auto_dismiss = True
|
|||
|
"""
|
|||
|
|
|||
|
_firebase_requirements = """kivy==2.1.0
|
|||
|
kivymd==1.0.0
|
|||
|
multitasking
|
|||
|
firebase
|
|||
|
firebase-admin
|
|||
|
python_jwt
|
|||
|
gcloud
|
|||
|
sseclient
|
|||
|
pycryptodome==3.4.3
|
|||
|
requests_toolbelt
|
|||
|
"""
|
|||
|
|
|||
|
_without_firebase_requirements = """kivy==2.1.0
|
|||
|
kivymd==1.0.0
|
|||
|
"""
|
|||
|
|
|||
|
_hot_reload_main = '''
|
|||
|
"""
|
|||
|
Script for managing hot reloading of the project.
|
|||
|
For more details see the documentation page -
|
|||
|
|
|||
|
https://kivymd.readthedocs.io/en/latest/api/kivymd/tools/patterns/create_project/
|
|||
|
|
|||
|
To run the application in hot boot mode, execute the command in the console:
|
|||
|
DEBUG=1 python main.py
|
|||
|
"""
|
|||
|
|
|||
|
import importlib
|
|||
|
import os
|
|||
|
|
|||
|
from kivy import Config
|
|||
|
from kivy.uix.screenmanager import ScreenManager
|
|||
|
|
|||
|
from PIL import ImageGrab
|
|||
|
|
|||
|
# TODO: You may know an easier way to get the size of a computer display.
|
|||
|
resolution = ImageGrab.grab().size
|
|||
|
|
|||
|
# Change the values of the application window size as you need.
|
|||
|
Config.set("graphics", "height", resolution[1])
|
|||
|
Config.set("graphics", "width", "400")
|
|||
|
|
|||
|
from kivy.core.window import Window%s
|
|||
|
|
|||
|
# Place the application window on the right side of the computer screen.
|
|||
|
Window.top = 0
|
|||
|
Window.left = resolution[0] - Window.width
|
|||
|
|
|||
|
from kivymd.tools.hotreload.app import MDApp
|
|||
|
%s%s
|
|||
|
|
|||
|
class %s(MDApp):
|
|||
|
KV_FILES = {
|
|||
|
os.path.join(
|
|||
|
os.getcwd(),
|
|||
|
"View",
|
|||
|
"%s",
|
|||
|
"%s.kv",
|
|||
|
),
|
|||
|
}%s
|
|||
|
|
|||
|
def build_app(self) -> ScreenManager:
|
|||
|
"""
|
|||
|
In this method, you don't need to change anything other than the
|
|||
|
application theme.
|
|||
|
"""
|
|||
|
|
|||
|
import View.screens
|
|||
|
|
|||
|
self.theme_cls.primary_palette = "Orange"
|
|||
|
self.manager_screens = ScreenManager()%s%s
|
|||
|
Window.bind(on_key_down=self.on_keyboard_down)
|
|||
|
importlib.reload(View.screens)
|
|||
|
screens = View.screens.screens
|
|||
|
|
|||
|
for i, name_screen in enumerate(screens.keys()):
|
|||
|
model = screens[name_screen]["model"](%s)
|
|||
|
controller = screens[name_screen]["controller"](model)
|
|||
|
view = controller.get_view()
|
|||
|
view.manager_screens = self.manager_screens
|
|||
|
view.name = name_screen
|
|||
|
self.manager_screens.add_widget(view)
|
|||
|
|
|||
|
return self.manager_screens
|
|||
|
|
|||
|
def on_keyboard_down(self, window, keyboard, keycode, text, modifiers) -> None:
|
|||
|
"""
|
|||
|
The method handles keyboard events.
|
|||
|
|
|||
|
By default, a forced restart of an application is tied to the
|
|||
|
`CTRL+R` key on Windows OS and `COMMAND+R` on Mac OS.
|
|||
|
"""
|
|||
|
|
|||
|
if "meta" in modifiers or "ctrl" in modifiers and text == "r":
|
|||
|
self.rebuild()%s%s
|
|||
|
|
|||
|
|
|||
|
%s().run()
|
|||
|
|
|||
|
# After you finish the project, remove the above code and uncomment the below
|
|||
|
# code to test the application normally without hot reloading.
|
|||
|
'''
|
|||
|
|
|||
|
available_patterns = ["MVC"]
|
|||
|
available_databases = ["firebase", "restdb"]
|
|||
|
|
|||
|
|
|||
|
def main():
|
|||
|
parser = create_argument_parser()
|
|||
|
args = parser.parse_args()
|
|||
|
|
|||
|
pattern_name = args.pattern
|
|||
|
project_directory = args.directory
|
|||
|
project_name = "".join(args.name.split(" "))
|
|||
|
kivy_version = args.kivy_version
|
|||
|
python_version = args.python_version
|
|||
|
if "3" not in python_version:
|
|||
|
parser.error("Python must be at least version 3")
|
|||
|
name_screen = "".join(args.name_screen.split(" "))
|
|||
|
path_to_project = os.path.join(project_directory, project_name)
|
|||
|
name_database = args.name_database
|
|||
|
if name_database != "no" and name_database not in available_databases:
|
|||
|
parser.error(
|
|||
|
f"The database name must be one of the {available_databases} list"
|
|||
|
)
|
|||
|
use_hotreload = args.use_hotreload
|
|||
|
use_localization = args.use_localization
|
|||
|
|
|||
|
# Check arguments.
|
|||
|
if name_screen[-6:] != "Screen":
|
|||
|
parser.error(
|
|||
|
"Name of the screen must contain the word 'Screen' at the end. "
|
|||
|
"\nFor example - '... --name_screen MyFirstScreen'"
|
|||
|
)
|
|||
|
module_name = chek_camel_case_name_project(name_screen)
|
|||
|
if not module_name:
|
|||
|
parser.error(
|
|||
|
"The name of the screen should be written in camel case style. "
|
|||
|
"\nFor example - 'MyFirstScreen'"
|
|||
|
)
|
|||
|
module_name = "_".join([name.lower() for name in module_name])
|
|||
|
if not os.path.exists(
|
|||
|
os.path.join(kivymd_path, "tools", "patterns", pattern_name)
|
|||
|
):
|
|||
|
parser.error(
|
|||
|
f"There is no {pattern_name} pattern.\n"
|
|||
|
f"Only {available_patterns} template is available."
|
|||
|
)
|
|||
|
|
|||
|
# Call the functions of creating a project.
|
|||
|
if not os.path.exists(path_to_project):
|
|||
|
shutil.copytree(
|
|||
|
os.path.join(kivymd_path, "tools", "patterns", pattern_name),
|
|||
|
path_to_project,
|
|||
|
)
|
|||
|
create_main(
|
|||
|
name_database, use_localization, path_to_project, project_name
|
|||
|
)
|
|||
|
create_model(name_database, name_screen, module_name, path_to_project)
|
|||
|
create_controller(
|
|||
|
name_database,
|
|||
|
use_hotreload,
|
|||
|
name_screen,
|
|||
|
module_name,
|
|||
|
path_to_project,
|
|||
|
)
|
|||
|
create_view(
|
|||
|
name_database,
|
|||
|
use_localization,
|
|||
|
name_screen,
|
|||
|
module_name,
|
|||
|
path_to_project,
|
|||
|
)
|
|||
|
create_requirements(name_database, path_to_project)
|
|||
|
os.makedirs(os.path.join(path_to_project, "assets", "images"))
|
|||
|
os.mkdir(os.path.join(path_to_project, "assets", "fonts"))
|
|||
|
rename_ext_py_tmp_to_py(path_to_project)
|
|||
|
move_init(path_to_project, name_screen)
|
|||
|
|
|||
|
if name_database != "no":
|
|||
|
check_databases(name_database, path_to_project)
|
|||
|
|
|||
|
if use_hotreload == "yes":
|
|||
|
create_main_with_hotreload(
|
|||
|
path_to_project,
|
|||
|
project_name,
|
|||
|
name_screen,
|
|||
|
module_name,
|
|||
|
name_database,
|
|||
|
use_localization,
|
|||
|
)
|
|||
|
with open(
|
|||
|
os.path.join(path_to_project, "requirements.txt"),
|
|||
|
"a",
|
|||
|
encoding="utf-8",
|
|||
|
) as requirements:
|
|||
|
requirements.write("watchdog")
|
|||
|
|
|||
|
if use_localization == "yes":
|
|||
|
Logger.info("KivyMD: Create localization files...")
|
|||
|
create_makefile(
|
|||
|
path_to_project, project_name, module_name, name_screen
|
|||
|
)
|
|||
|
localization_po_file(path_to_project)
|
|||
|
create_mofile(path_to_project)
|
|||
|
else:
|
|||
|
os.remove(os.path.join(path_to_project, "messages.pot"))
|
|||
|
os.remove(os.path.join(path_to_project, "libs", "translation.py"))
|
|||
|
shutil.rmtree(os.path.join(path_to_project, "data"))
|
|||
|
Logger.info(f"KivyMD: Project '{path_to_project}' created")
|
|||
|
Logger.info(
|
|||
|
f"KivyMD: Create a virtual environment for '{path_to_project}' project..."
|
|||
|
)
|
|||
|
create_virtual_environment(python_version, path_to_project)
|
|||
|
Logger.info(
|
|||
|
f"KivyMD: Install requirements for '{path_to_project}' project..."
|
|||
|
)
|
|||
|
install_requirements(path_to_project, kivy_version, name_database)
|
|||
|
else:
|
|||
|
parser.error(f"The {path_to_project} project already exists")
|
|||
|
|
|||
|
|
|||
|
def create_main_with_hotreload(
|
|||
|
path_to_project: str,
|
|||
|
project_name: str,
|
|||
|
name_screen: str,
|
|||
|
module_name: str,
|
|||
|
name_database: str,
|
|||
|
use_localization: str,
|
|||
|
) -> None:
|
|||
|
with open(
|
|||
|
os.path.join(path_to_project, "main.py"), encoding="utf-8"
|
|||
|
) as main_file:
|
|||
|
main_code = ""
|
|||
|
for string in main_file.readlines():
|
|||
|
main_code += f"# {string}"
|
|||
|
with open(
|
|||
|
os.path.join(path_to_project, "main.py"), "w", encoding="utf-8"
|
|||
|
) as main_file:
|
|||
|
main_file.write(f"{_hot_reload_main}\n{main_code}")
|
|||
|
|
|||
|
replace_in_file(
|
|||
|
os.path.join(path_to_project, "main.py"),
|
|||
|
(
|
|||
|
"\nfrom kivy.properties import StringProperty"
|
|||
|
if use_localization == "yes"
|
|||
|
else "",
|
|||
|
"\nfrom Model.database import DataBase"
|
|||
|
if name_database != "no"
|
|||
|
else "",
|
|||
|
"\nfrom libs.translation import Translation\n"
|
|||
|
if use_localization == "yes"
|
|||
|
else "",
|
|||
|
project_name,
|
|||
|
name_screen,
|
|||
|
module_name,
|
|||
|
'\n lang = StringProperty("en")\n'
|
|||
|
if use_localization == "yes"
|
|||
|
else "",
|
|||
|
"\n self.base = DataBase()\n"
|
|||
|
if name_database != "no"
|
|||
|
else "",
|
|||
|
"\n self.translation = Translation(\n"
|
|||
|
' self.lang, "%s", f"{self.directory}/data/locales"'
|
|||
|
"\n )" % project_name
|
|||
|
if use_localization == "yes"
|
|||
|
else "",
|
|||
|
"self.database" if name_database != "no" else "",
|
|||
|
"\n\n def on_lang(self, instance_app, lang_value: str) -> None:\n"
|
|||
|
" self.translation.switch_lang(lang_value)\n"
|
|||
|
if use_localization == "yes"
|
|||
|
else "",
|
|||
|
"\n def switch_lang(self) -> None:\n"
|
|||
|
' """Switch lang."""\n\n'
|
|||
|
' self.lang = "ru" if self.lang == "en" else "en"'
|
|||
|
if use_localization == "yes"
|
|||
|
else "",
|
|||
|
project_name,
|
|||
|
),
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
def create_main(
|
|||
|
name_database: str,
|
|||
|
use_localization: str,
|
|||
|
path_to_project: str,
|
|||
|
project_name: str,
|
|||
|
) -> None:
|
|||
|
replace_in_file(
|
|||
|
os.path.join(path_to_project, "main.py_tmp"),
|
|||
|
(
|
|||
|
"\nfrom kivy.properties import StringProperty"
|
|||
|
if use_localization == "yes"
|
|||
|
else "",
|
|||
|
"\nfrom libs.translation import Translation"
|
|||
|
if use_localization == "yes"
|
|||
|
else "",
|
|||
|
"from Model.database import DataBase\n"
|
|||
|
if name_database != "no"
|
|||
|
else "",
|
|||
|
project_name,
|
|||
|
'\n lang = StringProperty("en")\n'
|
|||
|
if use_localization == "yes"
|
|||
|
else "",
|
|||
|
"\n self.translation = Translation(\n"
|
|||
|
' self.lang, "%s", f"{self.directory}/data/locales"'
|
|||
|
"\n )" % project_name
|
|||
|
if use_localization == "yes"
|
|||
|
else "",
|
|||
|
"self.database = DataBase()\n" if name_database != "no" else "",
|
|||
|
"self.database" if name_database != "no" else "",
|
|||
|
"\n def on_lang(self, instance_app, lang_value: str) -> None:\n"
|
|||
|
" self.translation.switch_lang(lang_value)\n"
|
|||
|
if use_localization == "yes"
|
|||
|
else "",
|
|||
|
"\n def switch_lang(self) -> None:\n"
|
|||
|
' """Switch lang."""\n\n'
|
|||
|
' self.lang = "ru" if self.lang == "en" else "en"\n'
|
|||
|
if use_localization == "yes"
|
|||
|
else "",
|
|||
|
project_name,
|
|||
|
),
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
def create_model(
|
|||
|
name_database: str, name_screen: str, module_name: str, path_to_project: str
|
|||
|
) -> None:
|
|||
|
if name_database != "no":
|
|||
|
database_model = _database_model.format(
|
|||
|
name_screen=name_screen,
|
|||
|
module_name=module_name,
|
|||
|
notify_name_screen=f'"{" ".join(module_name.split("_"))}"',
|
|||
|
)
|
|||
|
replace_in_file(
|
|||
|
os.path.join(path_to_project, "Model", "first_screen.py_tmp"),
|
|||
|
(database_model),
|
|||
|
)
|
|||
|
else:
|
|||
|
without_database_model = _without_database_model.format(
|
|||
|
module_name=module_name, name_screen=name_screen
|
|||
|
)
|
|||
|
replace_in_file(
|
|||
|
os.path.join(path_to_project, "Model", "first_screen.py_tmp"),
|
|||
|
(without_database_model),
|
|||
|
)
|
|||
|
os.remove(
|
|||
|
os.path.join(path_to_project, "Model", "database_firebase.py")
|
|||
|
)
|
|||
|
os.remove(os.path.join(path_to_project, "Model", "database_restdb.py"))
|
|||
|
os.rename(
|
|||
|
os.path.join(path_to_project, "Model", "first_screen.py_tmp"),
|
|||
|
os.path.join(path_to_project, "Model", f"{module_name}.py_tmp"),
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
def create_controller(
|
|||
|
name_database: str,
|
|||
|
use_hotreload: str,
|
|||
|
name_screen: str,
|
|||
|
module_name: str,
|
|||
|
path_to_project: str,
|
|||
|
) -> None:
|
|||
|
if name_database != "no":
|
|||
|
database_controller = _database_controller
|
|||
|
else:
|
|||
|
database_controller = _without_database_controller
|
|||
|
name_view = (
|
|||
|
f"View.{name_screen}.{module_name}.{name_screen}View"
|
|||
|
if use_hotreload == "yes"
|
|||
|
else f"{name_screen}View"
|
|||
|
)
|
|||
|
database_controller = database_controller.format(
|
|||
|
name_screen=name_screen,
|
|||
|
module_name=module_name,
|
|||
|
import_module=""
|
|||
|
f"import importlib\n\n"
|
|||
|
f"import View.{name_screen}.{module_name}\n\n"
|
|||
|
f"# We have to manually reload the view module in order to apply the\n"
|
|||
|
f"# changes made to the code on a subsequent hot reload.\n"
|
|||
|
f"# If you no longer need a hot reload, you can delete this instruction.\n"
|
|||
|
f"importlib.reload(View.{name_screen}.{module_name})"
|
|||
|
if use_hotreload == "yes"
|
|||
|
else f"\nfrom View.{name_screen}.{module_name} import {name_screen}View",
|
|||
|
name_view=name_view,
|
|||
|
)
|
|||
|
replace_in_file(
|
|||
|
os.path.join(path_to_project, "Controller", "first_screen.py_tmp"),
|
|||
|
(database_controller, name_view),
|
|||
|
)
|
|||
|
os.rename(
|
|||
|
os.path.join(path_to_project, "Controller", "first_screen.py_tmp"),
|
|||
|
os.path.join(path_to_project, "Controller", f"{module_name}.py_tmp"),
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
def create_view(
|
|||
|
name_database: str,
|
|||
|
use_localization: str,
|
|||
|
name_screen: str,
|
|||
|
module_name: str,
|
|||
|
path_to_project: str,
|
|||
|
) -> None:
|
|||
|
replace_in_file(
|
|||
|
os.path.join(path_to_project, "View", "screens.py_tmp"),
|
|||
|
(
|
|||
|
module_name,
|
|||
|
f"{name_screen}Model",
|
|||
|
module_name,
|
|||
|
f"{name_screen}Controller",
|
|||
|
f'"{" ".join(module_name.split("_"))}"',
|
|||
|
f"{name_screen}Model",
|
|||
|
f"{name_screen}Controller",
|
|||
|
),
|
|||
|
)
|
|||
|
replace_in_file(
|
|||
|
os.path.join(path_to_project, "View", "FirstScreen", "first_screen.kv"),
|
|||
|
(
|
|||
|
f"{name_screen}View",
|
|||
|
name_screen,
|
|||
|
"app.switch_lang()" if use_localization == "yes" else "x",
|
|||
|
'app.translation._("To log in, enter your personal data:")'
|
|||
|
if use_localization == "yes"
|
|||
|
else '"To log in, enter your personal data:"',
|
|||
|
'app.translation._("Login")'
|
|||
|
if use_localization == "yes"
|
|||
|
else '"Login"',
|
|||
|
'app.translation._("Password")'
|
|||
|
if use_localization == "yes"
|
|||
|
else '"Password"',
|
|||
|
'app.translation._("LOGIN")'
|
|||
|
if use_localization == "yes"
|
|||
|
else '"LOGIN"',
|
|||
|
),
|
|||
|
)
|
|||
|
replace_in_file(
|
|||
|
os.path.join(
|
|||
|
path_to_project, "View", "FirstScreen", "first_screen.py_tmp"
|
|||
|
),
|
|||
|
(
|
|||
|
_database_view_import
|
|||
|
if name_database != "no"
|
|||
|
else _without_database_view_import,
|
|||
|
f"{name_screen}View",
|
|||
|
_database_view_methods if name_database != "no" else "",
|
|||
|
_database_view_model_is_changed_method
|
|||
|
if name_database != "no"
|
|||
|
else "",
|
|||
|
),
|
|||
|
)
|
|||
|
replace_in_file(
|
|||
|
os.path.join(path_to_project, "View", "base_screen.py_tmp"),
|
|||
|
(
|
|||
|
module_name,
|
|||
|
f"{name_screen}Model",
|
|||
|
module_name,
|
|||
|
f"{name_screen}Controller",
|
|||
|
module_name,
|
|||
|
f"{name_screen}Model",
|
|||
|
),
|
|||
|
)
|
|||
|
os.rename(
|
|||
|
os.path.join(path_to_project, "View", "base_screen.py_tmp"),
|
|||
|
os.path.join(path_to_project, "View", "base_screen.py"),
|
|||
|
)
|
|||
|
os.rename(
|
|||
|
os.path.join(path_to_project, "View", "FirstScreen", "first_screen.kv"),
|
|||
|
os.path.join(
|
|||
|
path_to_project, "View", "FirstScreen", f"{module_name}.kv"
|
|||
|
),
|
|||
|
)
|
|||
|
os.rename(
|
|||
|
os.path.join(
|
|||
|
path_to_project, "View", "FirstScreen", "first_screen.py_tmp"
|
|||
|
),
|
|||
|
os.path.join(
|
|||
|
path_to_project, "View", "FirstScreen", f"{module_name}.py_tmp"
|
|||
|
),
|
|||
|
)
|
|||
|
os.rename(
|
|||
|
os.path.join(path_to_project, "View", "FirstScreen"),
|
|||
|
os.path.join(path_to_project, "View", name_screen),
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
def create_requirements(name_database: str, path_to_project: str) -> None:
|
|||
|
with open(
|
|||
|
os.path.join(path_to_project, "requirements.txt"), "w", encoding="utf-8"
|
|||
|
) as requirements:
|
|||
|
requirements.write(
|
|||
|
_firebase_requirements
|
|||
|
if name_database != "no"
|
|||
|
else _without_firebase_requirements
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
def create_makefile(
|
|||
|
path_to_project: str, project_name: str, module_name: str, name_screen: str
|
|||
|
) -> None:
|
|||
|
path_to_makefile_tmp = os.path.join(path_to_project, "Makefile.tmp")
|
|||
|
replace_in_file(
|
|||
|
path_to_makefile_tmp,
|
|||
|
(
|
|||
|
name_screen,
|
|||
|
module_name,
|
|||
|
name_screen,
|
|||
|
module_name,
|
|||
|
project_name,
|
|||
|
project_name,
|
|||
|
),
|
|||
|
)
|
|||
|
os.rename(path_to_makefile_tmp, os.path.splitext(path_to_makefile_tmp)[0])
|
|||
|
os.chdir(path_to_project)
|
|||
|
os.system("make po")
|
|||
|
|
|||
|
|
|||
|
def create_mofile(path_to_project: str) -> None:
|
|||
|
os.chdir(path_to_project)
|
|||
|
os.system("make mo")
|
|||
|
|
|||
|
|
|||
|
def create_virtual_environment(
|
|||
|
python_version: str, path_to_project: str
|
|||
|
) -> None:
|
|||
|
os.system(f"{python_version} -m pip install virtualenv")
|
|||
|
os.system(
|
|||
|
f"virtualenv -p {python_version} {os.path.join(path_to_project, 'venv')}"
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
def localization_po_file(path_to_project: str) -> None:
|
|||
|
path_to_file_po = os.path.join(
|
|||
|
path_to_project, "data", "locales", "po", "ru.po"
|
|||
|
)
|
|||
|
with open(path_to_file_po, "rt", encoding="utf-8") as file_po:
|
|||
|
file_po_content = (
|
|||
|
file_po.read()
|
|||
|
.replace(
|
|||
|
'msgid "To log in, enter your personal data:"\nmsgstr ""',
|
|||
|
'msgid "To log in, enter your personal data:"\nmsgstr "Для входа введите свои личные данные"',
|
|||
|
)
|
|||
|
.replace(
|
|||
|
'msgid "Login"\nmsgstr ""', 'msgid "Login"\nmsgstr "Логин"'
|
|||
|
)
|
|||
|
.replace(
|
|||
|
'msgid "Password"\nmsgstr ""',
|
|||
|
'msgid "Password"\nmsgstr "Пароль"',
|
|||
|
)
|
|||
|
.replace(
|
|||
|
'msgid "LOGIN"\nmsgstr ""', 'msgid "LOGIN"\nmsgstr "ЛОГИН"'
|
|||
|
)
|
|||
|
)
|
|||
|
with open(path_to_file_po, "wt", encoding="utf-8") as file_po:
|
|||
|
file_po.write(file_po_content)
|
|||
|
|
|||
|
|
|||
|
def install_requirements(
|
|||
|
path_to_project: str, kivy_version: str, name_database: str
|
|||
|
) -> None:
|
|||
|
python = os.path.join(path_to_project, "venv", "bin", "python3")
|
|||
|
if kivy_version == "master":
|
|||
|
if platform == "macosx":
|
|||
|
os.system(
|
|||
|
f"{python} -m pip install 'kivy[base] @ https://github.com/kivy/kivy/archive/master.zip'"
|
|||
|
)
|
|||
|
else:
|
|||
|
os.system(
|
|||
|
f"{python} -m pip install https://github.com/kivy/kivy/archive/master.zip"
|
|||
|
)
|
|||
|
elif kivy_version == "stable":
|
|||
|
os.system(f"{python} -m pip install kivy")
|
|||
|
else:
|
|||
|
os.system(f"{python} -m pip install kivy=={kivy_version}")
|
|||
|
os.system(
|
|||
|
f"{python} -m pip install https://github.com/kivymd/KivyMD/archive/master.zip"
|
|||
|
)
|
|||
|
os.system(f"{python} -m pip install watchdog")
|
|||
|
if name_database == "firebase":
|
|||
|
os.system(
|
|||
|
f"{python} -m pip install "
|
|||
|
f"multitasking "
|
|||
|
f"firebase "
|
|||
|
f"firebase-admin "
|
|||
|
f"python_jwt "
|
|||
|
f"gcloud "
|
|||
|
f"sseclient "
|
|||
|
f"pycryptodome==3.4.3 "
|
|||
|
f"requests_toolbelt "
|
|||
|
f"watchdog "
|
|||
|
)
|
|||
|
os.system(
|
|||
|
f"{os.path.join(path_to_project, 'venv', 'bin', 'python3')} -m pip list"
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
def rename_ext_py_tmp_to_py(path_to_project: str) -> None:
|
|||
|
for path_to_dir, dirs, files in os.walk(path_to_project):
|
|||
|
for name_file in files:
|
|||
|
if os.path.splitext(name_file)[1] == ".py_tmp":
|
|||
|
os.rename(
|
|||
|
os.path.join(path_to_dir, name_file),
|
|||
|
os.path.join(
|
|||
|
path_to_dir, f"{os.path.splitext(name_file)[0]}.py"
|
|||
|
),
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
def move_init(path_to_project: str, name_screen: str) -> None:
|
|||
|
path_to_init_file = __file__.replace("create_project", "__init__")
|
|||
|
for name_dir in ("Controller", "Model", "Utility", "View"):
|
|||
|
shutil.copy(
|
|||
|
path_to_init_file,
|
|||
|
os.path.join(path_to_project, name_dir, "__init__.py"),
|
|||
|
)
|
|||
|
shutil.copy(
|
|||
|
path_to_init_file,
|
|||
|
os.path.join(path_to_project, "View", name_screen, "__init__.py"),
|
|||
|
)
|
|||
|
path_to_components = os.path.join(
|
|||
|
path_to_project, "View", name_screen, "components"
|
|||
|
)
|
|||
|
os.mkdir(path_to_components)
|
|||
|
shutil.copy(
|
|||
|
path_to_init_file, os.path.join(path_to_components, "__init__.py")
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
def check_databases(name_database: str, path_to_project: str) -> None:
|
|||
|
databases = {"firebase": "restdb", "restdb": "firebase"}
|
|||
|
os.remove(
|
|||
|
os.path.join(
|
|||
|
path_to_project, "Model", f"database_{databases[name_database]}.py"
|
|||
|
)
|
|||
|
)
|
|||
|
os.rename(
|
|||
|
os.path.join(path_to_project, "Model", f"database_{name_database}.py"),
|
|||
|
os.path.join(path_to_project, "Model", "database.py"),
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
def chek_camel_case_name_project(name_project) -> Union[bool, list]:
|
|||
|
result = re.findall("[A-Z][a-z]*", name_project)
|
|||
|
if len(result) == 1:
|
|||
|
return False
|
|||
|
return result
|
|||
|
|
|||
|
|
|||
|
def replace_in_file(path_to_file: str, args) -> None:
|
|||
|
with open(path_to_file, "rt", encoding="utf-8") as file_content:
|
|||
|
new_file_content = file_content.read() % (args)
|
|||
|
with open(path_to_file, "wt", encoding="utf-8") as original_file:
|
|||
|
original_file.write(new_file_content)
|
|||
|
|
|||
|
|
|||
|
def create_argument_parser() -> ArgumentParserWithHelp:
|
|||
|
parser = ArgumentParserWithHelp(
|
|||
|
prog="create_project.py",
|
|||
|
allow_abbrev=False,
|
|||
|
)
|
|||
|
parser.add_argument(
|
|||
|
"pattern",
|
|||
|
help="the name of the pattern with which the project will be created.",
|
|||
|
)
|
|||
|
parser.add_argument(
|
|||
|
"directory",
|
|||
|
help="directory in which the project will be created.",
|
|||
|
)
|
|||
|
parser.add_argument(
|
|||
|
"name",
|
|||
|
help="project name.",
|
|||
|
)
|
|||
|
parser.add_argument(
|
|||
|
"python_version",
|
|||
|
help="the version of Python (specify as `python3.9` or `python3.8`) "
|
|||
|
"with which the virtual environment will be created.",
|
|||
|
)
|
|||
|
parser.add_argument(
|
|||
|
"kivy_version",
|
|||
|
help="version of Kivy (specify as `2.1.0` or `master`) that will be "
|
|||
|
"used in the project.",
|
|||
|
)
|
|||
|
parser.add_argument(
|
|||
|
"--name_screen",
|
|||
|
default="MainScreen",
|
|||
|
help="the name of the class wich be used when creating the project pattern.",
|
|||
|
)
|
|||
|
parser.add_argument(
|
|||
|
"--name_database",
|
|||
|
default="no",
|
|||
|
help="name of the database provider ('firebase' or 'restdb').",
|
|||
|
)
|
|||
|
parser.add_argument(
|
|||
|
"--use_hotreload",
|
|||
|
default="no",
|
|||
|
help="creates a hot reload entry point to the application.",
|
|||
|
)
|
|||
|
parser.add_argument(
|
|||
|
"--use_localization",
|
|||
|
default="no",
|
|||
|
help="creates application localization files.",
|
|||
|
)
|
|||
|
return parser
|
|||
|
|
|||
|
|
|||
|
if __name__ == "__main__":
|
|||
|
main()
|