├── .gitignore ├── Controller ├── __init__.py ├── address.py ├── base.py ├── cost.py ├── dimension.py ├── estateviewmodel.py ├── experience.py ├── facilities.py ├── feature.py ├── home.py ├── images.py ├── services.py └── user.py ├── Model ├── __init__.py ├── address.py ├── base.py ├── cost.py ├── dimension.py ├── estateViewModel.py ├── experience.py ├── facilities.py ├── feature.py ├── home.py ├── images.py ├── services.py └── user.py ├── README.md ├── Test └── __init__.py ├── View └── __init__.py ├── arabic_reshaper.py ├── data ├── fonts │ └── Mirta.ttf └── images │ ├── h_line.png │ └── loading.gif ├── images ├── estates │ ├── 0f29e97a-8aa3-4c35-860f-6f2796d6bf8d-1410212873_home-512.png │ ├── 55000ff9-28ae-4f72-8b97-96c892c9ceac-margaret_mitchell_house_atlanta_2006.jpg │ ├── 5c4002f2-411b-4044-a853-a1f5773281a7-knowledge.jpg │ ├── 5e9cc024-d4fe-4807-b49c-e5b977324be0-1413076197_679595-camera-512.png │ ├── 81dc7988-8c7a-4d25-8eff-cee9b38fb96a-1413076197_679595-camera-512.png │ └── ee50eff9-6f76-4aa3-8f33-18285b9312ef-1410212873_home-512.png └── services │ ├── 0f29e97a-8aa3-4c35-860f-6f2796d6bf8d-1410212873_home-512.png │ ├── 5e9cc024-d4fe-4807-b49c-e5b977324be0-1413076197_679595-camera-512.png │ └── ee50eff9-6f76-4aa3-8f33-18285b9312ef-1410212873_home-512.png ├── main.py ├── melk.db ├── melk.kv └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | .DS_Store 104 | -------------------------------------------------------------------------------- /Controller/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/Controller/__init__.py -------------------------------------------------------------------------------- /Controller/address.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from Controller.base import BaseOperation 3 | 4 | class AddressOperation(BaseOperation): 5 | pass -------------------------------------------------------------------------------- /Controller/base.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from peewee import IntegrityError 3 | 4 | class BaseOperation(object): 5 | 6 | def add(self, Object, items): 7 | try: 8 | object_client = Object.create(**items) 9 | except IntegrityError: 10 | object_client = Object.get(Object.id == items['id']) 11 | object_server = Object() 12 | object_server.set(items) 13 | if object_server != object_client: 14 | self.update(Object, items) 15 | return object_client 16 | 17 | def update(self, Object, item): 18 | Object.update(**item)\ 19 | .where(Object.id == item['id'])\ 20 | .execute() -------------------------------------------------------------------------------- /Controller/cost.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from Controller.base import BaseOperation 3 | 4 | class CostOperation(BaseOperation): 5 | pass -------------------------------------------------------------------------------- /Controller/dimension.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from Controller.base import BaseOperation 3 | 4 | class DimensionOperation(BaseOperation): 5 | pass -------------------------------------------------------------------------------- /Controller/estateviewmodel.py: -------------------------------------------------------------------------------- 1 | # _*_ coding: UTF8 _*_ 2 | from Model.estateViewModel import EstateViewModel 3 | from Model import get_image, get_local_filename 4 | from Model.base import DATABASE 5 | from peewee import IntegrityError 6 | from Controller.base import BaseOperation 7 | 8 | 9 | class EstateOperation(BaseOperation): 10 | 11 | def add(self, records): 12 | estate_list =[] 13 | for item in records: 14 | item['image_url'] = get_image(item.get('image_url')) 15 | estate = super(EstateOperation, self).add(EstateViewModel, item) 16 | estate_list.append(estate) 17 | 18 | #FIXME: if data on server and client is same must return local data 19 | #FIXED: if return data from add method is empty system call restore_local_data 20 | return estate_list 21 | 22 | def index(self): 23 | return EstateViewModel.select() -------------------------------------------------------------------------------- /Controller/experience.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from Controller.base import BaseOperation 3 | 4 | class ExperienceOperation(BaseOperation): 5 | pass -------------------------------------------------------------------------------- /Controller/facilities.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from Model.facilities import Facilities 3 | from peewee import IntegrityError 4 | from Controller.base import BaseOperation 5 | 6 | class FacilitiesOperation(BaseOperation): 7 | pass -------------------------------------------------------------------------------- /Controller/feature.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from Model.feature import Feature 3 | from Controller.home import HomeOperation 4 | from Controller.address import AddressOperation 5 | from Controller.cost import CostOperation 6 | from Controller.dimension import DimensionOperation 7 | from Controller.experience import ExperienceOperation 8 | from Controller.facilities import FacilitiesOperation 9 | from Controller.user import UserOperation 10 | from Controller.services import ServiceOperation 11 | from Model.facilities import Facilities 12 | from Model.address import Address 13 | from Model.cost import Cost 14 | from Model.dimension import Dimension 15 | from Model.experience import Experience 16 | from Model.home import Home 17 | from Model.user import User 18 | from Controller.base import BaseOperation 19 | 20 | home_operation = HomeOperation() 21 | address_operation = AddressOperation() 22 | cost_operation = CostOperation() 23 | dimension_operation = DimensionOperation() 24 | experience_operation = ExperienceOperation() 25 | facilities_operation = FacilitiesOperation() 26 | user_operation = UserOperation() 27 | service_operation = ServiceOperation() 28 | 29 | class FeatureOperation(BaseOperation): 30 | 31 | def add(self, melk): 32 | feature = super(FeatureOperation, self).add(Feature, melk['feature']) 33 | self.add_other_attributes(melk, feature) 34 | return feature 35 | 36 | def add_other_attributes(self, melk, feature): 37 | if melk.get('address'): 38 | address = address_operation.add(Address, melk.get('address')) 39 | feature.address = address 40 | if melk.get('cost'): 41 | cost = cost_operation.add(Cost, melk.get('cost')) 42 | feature.cost = cost 43 | if melk.get('dimension'): 44 | dimension = dimension_operation.add(Dimension, melk.get('dimension')) 45 | feature.dimension = dimension 46 | if melk.get('experience'): 47 | experience = experience_operation.add(Experience, melk.get('experience')) 48 | feature.experience = experience 49 | if melk.get('facilities'): 50 | facilities = facilities_operation.add(Facilities, melk.get('facilities')) 51 | feature.facilities = facilities 52 | if melk.get('home'): 53 | home = home_operation.add(Home, melk.get('home')) 54 | feature.home = home 55 | if melk.get('services'): 56 | for service in melk.get('services'): 57 | service_operation.add(service, feature) 58 | if melk.get('user'): 59 | user = user_operation.add(User, melk.get('user')) 60 | feature.user = user 61 | feature.save() 62 | 63 | def __getitem__(self, index): 64 | try: 65 | return Feature.get(Feature.id == index) 66 | except: 67 | return None -------------------------------------------------------------------------------- /Controller/home.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from Controller.base import BaseOperation 3 | 4 | class HomeOperation(BaseOperation): 5 | pass -------------------------------------------------------------------------------- /Controller/images.py: -------------------------------------------------------------------------------- 1 | #_coding: UTF8 _*_ 2 | from Model.images import Images 3 | from Model import get_image 4 | from peewee import IntegrityError 5 | from Controller.base import BaseOperation 6 | 7 | class ImageOperation(BaseOperation): 8 | 9 | def add(self, image, service): 10 | if image.get('file_name'): 11 | image['file_name'] = get_image(image['file_name'], 'images/services/') 12 | 13 | img = super(ImageOperation, self).add(Images, image) 14 | img.service = service 15 | img.save() 16 | 17 | return img -------------------------------------------------------------------------------- /Controller/services.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from Model.services import Service 3 | from Controller.images import ImageOperation 4 | from Controller.base import BaseOperation 5 | from peewee import IntegrityError 6 | 7 | image_operation = ImageOperation() 8 | 9 | class ServiceOperation(BaseOperation): 10 | 11 | def add(self, service, feature): 12 | items = service.copy() 13 | del items['images'] 14 | 15 | serv = super(ServiceOperation, self).add(Service, items) 16 | serv.feature = feature 17 | serv.save() 18 | 19 | self.add_images(service.get('images'), serv) 20 | return serv 21 | 22 | def add_images(self, images, service): 23 | for image in images: 24 | image = image_operation.add(image, service) 25 | 26 | -------------------------------------------------------------------------------- /Controller/user.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from Controller.base import BaseOperation 3 | 4 | class UserOperation(BaseOperation): 5 | pass -------------------------------------------------------------------------------- /Model/__init__.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import shutil 3 | import os 4 | 5 | from . import estateViewModel 6 | from .base import DATABASE 7 | import Model 8 | 9 | def get_image(file_name, local_dir = 'images/estates/'): 10 | response = requests.get(''.join([estateViewModel.WEBSITE, file_name]), stream = True) 11 | if response.status_code == 200: 12 | local_filename = get_local_filename(file_name, local_dir) 13 | with open(local_filename, 'wb') as image_file: 14 | response.raw.decode_content = True 15 | shutil.copyfileobj(response.raw, image_file) 16 | return local_filename 17 | 18 | def get_local_filename(file_name, local_dir): 19 | _ = file_name.split('/') 20 | if not os.path.exists(local_dir): 21 | os.makedirs(local_dir) 22 | local_filename = '/'.join([local_dir, _[-1]]) 23 | return local_filename 24 | 25 | def initial_db(): 26 | models = get_models() 27 | DATABASE.connect() 28 | DATABASE.create_tables(models, safe=True) 29 | 30 | def get_models(base_class = Model.base.BaseModel): 31 | import inspect 32 | import importlib 33 | models = [] 34 | files = get_files() 35 | 36 | for file in files: 37 | module = importlib.import_module(file) 38 | for name, obj in inspect.getmembers(module): 39 | if inspect.isclass(obj) and (obj is not base_class) and issubclass(obj, base_class): 40 | models.append(obj) 41 | return models 42 | 43 | 44 | def get_files(module_path = os.path.dirname(__file__), package = 'Model'): 45 | files = [] 46 | for file in os.listdir(module_path): 47 | module, extension = os.path.splitext(file) 48 | if os.path.isfile(os.path.join(module_path,file)) and extension == '.py': 49 | files.append('.'.join([package, module])) 50 | return files -------------------------------------------------------------------------------- /Model/address.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from .base import BaseModel 3 | from peewee import PrimaryKeyField, CharField 4 | 5 | class Address(BaseModel): 6 | id = PrimaryKeyField() 7 | country = CharField(max_length = 64) 8 | province = CharField(max_length = 64) 9 | city = CharField(max_length = 64) 10 | region = CharField(max_length = 64, null = True) 11 | postal_code = CharField(max_length = 64, null = True) 12 | addr = CharField(max_length = 200) 13 | 14 | def __unicode__(self): 15 | return self.addr -------------------------------------------------------------------------------- /Model/base.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from peewee import SqliteDatabase 3 | from peewee import Model 4 | 5 | DATABASE = SqliteDatabase('melk.db') 6 | 7 | class BaseModel(Model): 8 | class Meta: 9 | database = DATABASE 10 | 11 | def __eq__(self, other): 12 | return self.__dict__.get('_data') == other.__dict__.get('_data') 13 | 14 | def set(self, kwargs): 15 | self.__dict__.get('_data').update(kwargs) -------------------------------------------------------------------------------- /Model/cost.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from .base import BaseModel 3 | from peewee import PrimaryKeyField, BigIntegerField 4 | 5 | class Cost(BaseModel): 6 | id = PrimaryKeyField() 7 | cost_metric = BigIntegerField() 8 | price = BigIntegerField() 9 | 10 | def __unicode__(self): 11 | return self.price 12 | 13 | -------------------------------------------------------------------------------- /Model/dimension.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from .base import BaseModel 3 | from peewee import PrimaryKeyField, FloatField 4 | 5 | class Dimension(BaseModel): 6 | id = PrimaryKeyField() 7 | height = FloatField() 8 | width = FloatField() 9 | 10 | def __unicode__(self): 11 | return '{0} X {1}'.format(self.width, self.height) -------------------------------------------------------------------------------- /Model/estateViewModel.py: -------------------------------------------------------------------------------- 1 | # _*_ coding: UTF8 _*_ 2 | from Model.base import BaseModel 3 | from peewee import PrimaryKeyField, IntegerField, BooleanField, CharField 4 | 5 | WEBSITE = "http://localhost:8888" 6 | ESTATETYPE = [u'نامشخص', 7 | u'خانه', 8 | u'آپارتمان', 9 | u'زمین', 10 | u'تجاری'] 11 | 12 | SITUATION = [u'فروش', 13 | u'رهن'] 14 | 15 | 16 | class EstateViewModel(BaseModel): 17 | id = PrimaryKeyField() 18 | priority = IntegerField(default = 1) 19 | type = IntegerField(default = 1) 20 | sell_or_rent = BooleanField(default = False) 21 | area = IntegerField() 22 | price = IntegerField() 23 | image_url = CharField(max_length=128) 24 | is_sold = BooleanField(default = False) 25 | bedroom = IntegerField() 26 | 27 | def get_image_url(self): 28 | #return ''.join([WEBSITE, self.image_url]) 29 | return self.image_url 30 | 31 | def get_estate_type(self): 32 | return u' [color=000]-[/color] '.join([ESTATETYPE[self.type], self.get_situation()]) 33 | 34 | def get_situation(self): 35 | return SITUATION[self.sell_or_rent] 36 | 37 | def get_price(self): 38 | _ = u'[color=000]:اجاره [/color]' if self.sell_or_rent else u'[color=000]:قیمت [/color]' 39 | __ = u'[color=000] تومان[/color]' 40 | return ' '.join([__, str(self.price), _]) 41 | 42 | def get_area(self): 43 | _ = u'[color=000]:مساحت [/color]' 44 | __ = u'[color=000] مترمربع[/color]' 45 | return ' '.join([__, str(self.area), _]) -------------------------------------------------------------------------------- /Model/experience.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from .base import BaseModel 3 | from peewee import PrimaryKeyField, BooleanField 4 | 5 | class Experience(BaseModel): 6 | id = PrimaryKeyField() 7 | water = BooleanField(default = False) 8 | power = BooleanField(default = False) 9 | phone = BooleanField(default = False) 10 | gas = BooleanField(default = False) 11 | 12 | def __unicode__(self): 13 | return 'water {0}, power {1}, phone {2}, gas {3}'.format(self.water, 14 | self.power, 15 | self.phone, 16 | self.gas) -------------------------------------------------------------------------------- /Model/facilities.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from peewee import (PrimaryKeyField, IntegerField, BooleanField) 3 | from .base import BaseModel 4 | 5 | class Facilities(BaseModel): 6 | id = PrimaryKeyField() 7 | bedroom = IntegerField(default = 0) 8 | heating_system = BooleanField(default = False) 9 | cooling_system = BooleanField(default = False) 10 | well = BooleanField(default = False) 11 | warehouse = BooleanField(default = False) 12 | basement = BooleanField(default = False) 13 | elevator = BooleanField(default = False) 14 | balcony = BooleanField(default = False) 15 | shooting = BooleanField(default = False) 16 | 17 | def __unicode__(self): 18 | return self.bedroom -------------------------------------------------------------------------------- /Model/feature.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from peewee import (PrimaryKeyField, IntegerField, BooleanField, TextField, 3 | ForeignKeyField) 4 | from .base import BaseModel 5 | from .address import Address 6 | from .cost import Cost 7 | from .dimension import Dimension 8 | from .experience import Experience 9 | from Model.facilities import Facilities 10 | from Model.user import User 11 | from Model.home import Home 12 | 13 | 14 | class Feature(BaseModel): 15 | id = PrimaryKeyField() 16 | type = IntegerField(default = 0) 17 | priority = IntegerField(default = 1) 18 | situation = BooleanField(default = False) # True => Sell and False => Rent 19 | document = IntegerField(default = 0) 20 | position = IntegerField(default = 0) 21 | area = IntegerField() 22 | built_up_area = IntegerField() 23 | control = IntegerField(default = 0) 24 | construction_date = IntegerField(default = 1300) 25 | description = TextField() 26 | is_show = BooleanField(default = False) 27 | is_sold = BooleanField(default = False) 28 | 29 | address = ForeignKeyField(Address, related_name = 'feature', null = True) 30 | cost = ForeignKeyField(Cost, related_name = 'feature', null = True) 31 | dimension = ForeignKeyField(Dimension, related_name = 'feature', null = True) 32 | experience = ForeignKeyField(Experience, related_name = 'feature', null = True) 33 | facilities = ForeignKeyField(Facilities, related_name = 'feature', null = True) 34 | home = ForeignKeyField(Home, related_name = 'feature', null = True) 35 | user = ForeignKeyField(User, related_name = 'feature', null = True) 36 | 37 | def __unicode__(self): 38 | return '{0} and area {1}'.format(self.type, self.area) 39 | 40 | def __eq__(self, other): 41 | _ = other.__dict__.get('_data').copy() 42 | __ = self.__dict__.get('_data').copy() 43 | del _['address'], _['cost'], _['dimension'], _['experience'], _['facilities'], _['home'], _['user'] 44 | return _ == __ -------------------------------------------------------------------------------- /Model/home.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from peewee import (PrimaryKeyField, IntegerField) 3 | from .base import BaseModel 4 | 5 | 6 | class Home(BaseModel): 7 | id = PrimaryKeyField() 8 | frame_type = IntegerField(default = 0) 9 | floor = IntegerField(default = 0) 10 | the_floor = IntegerField(default = 0) 11 | unit = IntegerField(default = 0) 12 | 13 | def __unicode__(self): 14 | return '{} floor is {}'.format(self.frame_type, self.floor) -------------------------------------------------------------------------------- /Model/images.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from peewee import (PrimaryKeyField, IntegerField, BooleanField, TextField, 3 | ForeignKeyField, CharField) 4 | from .base import BaseModel 5 | from .services import Service 6 | 7 | 8 | class Images(BaseModel): 9 | id = PrimaryKeyField() 10 | file_name = CharField(max_length = 128) 11 | is_default = BooleanField(default = False) 12 | service = ForeignKeyField(Service, related_name = 'images', null = True) 13 | 14 | def __unicode__(self): 15 | return self.is_default 16 | 17 | def __eq__(self, other): 18 | _ = other.__dict__.get('_data') 19 | del _['service'] 20 | return self.__dict__.get('_data') == _ -------------------------------------------------------------------------------- /Model/services.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from peewee import (PrimaryKeyField, IntegerField, TextField, ForeignKeyField) 3 | from .base import BaseModel 4 | from Model.feature import Feature 5 | 6 | 7 | class Service(BaseModel): 8 | id = PrimaryKeyField() 9 | type = IntegerField() 10 | description = TextField(null = True) 11 | feature = ForeignKeyField(Feature, related_name = 'services', null = True) 12 | 13 | def __unicode__(self): 14 | return self.type 15 | 16 | def __eq__(self, other): 17 | # self filled by json dictionary and not exists in database. as 18 | # self.__dict__.get('_data').update(kwargs) is used for fill them 19 | # and in the kwargs we haven't feature attribute thus self haven't this attribute 20 | _ = self.__dict__.get('_data') 21 | __ = other.__dict__.get('_data') 22 | del __['feature'] 23 | return _ == __ -------------------------------------------------------------------------------- /Model/user.py: -------------------------------------------------------------------------------- 1 | #_*_ coding: UTF8 _*_ 2 | from peewee import (PrimaryKeyField, IntegerField, CharField, 3 | BooleanField) 4 | from .base import BaseModel 5 | 6 | 7 | class User(BaseModel): 8 | id = PrimaryKeyField(null = True) 9 | username = CharField(max_length = 32) 10 | first_name = CharField(max_length = 64, null = True) 11 | last_name = CharField(max_length = 64, null = True) 12 | password = CharField(max_length = 64, null = True) 13 | image_url = CharField(max_length = 200, null = True) 14 | 15 | country = CharField(max_length = 128, null = True) 16 | province = CharField(max_length = 128, null = True) 17 | city = CharField(max_length = 128, null = True) 18 | region = CharField(max_length = 128, null = True) 19 | postal_code = CharField(max_length = 32, null = True) 20 | addr = CharField(max_length = 200, null = True) 21 | 22 | city_code = IntegerField(null = True) 23 | tel = IntegerField(null = True) 24 | 25 | is_active = BooleanField(default = False) 26 | 27 | def __unicode__(self): 28 | return self.username -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kivy-simple-mvc-template 2 | simple mvc starter template 3 | 4 | This app is made for a real estate web service, Kivy is used to implement it. This project uses MVC pattern, Which can be a starter template to start working with the kivy. 5 | 6 | This project uses peewee as a ORM, And SQLite as database engine. 7 | 8 | Firstly, app will get real estates from the Web service and, if it has new real estates, it will store new real estates info in the database. 9 | 10 | ## Requirements 11 | 12 | * [Python 2.7](https://www.python.org/downloads/release/python-2712/) 13 | * [Kivy](https://kivy.org/#home) 14 | * [Pillow](https://python-pillow.org/) 15 | * [PyGame](https://www.pygame.org/news) 16 | * [Python-bidi](https://pypi.python.org/pypi/python-bidi) 17 | * [PeeWee](https://github.com/coleifer/peewee) 18 | 19 | ## Kivy Installation 20 | 21 | Install Kivy via the [this](https://kivy.org/#download) link 22 | 23 | please refer to the installation instructions for your specific platform 24 | 25 | > To install a kivy package, we recommend using the virtualenv tool 26 | 27 | ## Install other requirements 28 | In the root directory of project, run below code in terminal 29 | 30 | `pip install -r requirements.txt` 31 | 32 | ## Run the project 33 | In the root directory of project, run below code in terminal 34 | 35 | `python main.py` 36 | 37 | ## Packaging the application 38 | Using [this](https://kivy.org/docs/guide/packaging.html) link for packaging the application for Win, Mac, iOS, Android Platforms 39 | -------------------------------------------------------------------------------- /Test/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/Test/__init__.py -------------------------------------------------------------------------------- /View/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/View/__init__.py -------------------------------------------------------------------------------- /arabic_reshaper.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # This work is licensed under the GNU Public License (GPL). 4 | # To view a copy of this license, visit http://www.gnu.org/copyleft/gpl.html 5 | 6 | # Written by Abd Allah Diab (mpcabd) 7 | # Email: mpcabd ^at^ gmail ^dot^ com 8 | # Website: http://mpcabd.igeex.biz 9 | 10 | # Ported and tweaked from Java to Python, from Better Arabic Reshaper [https://github.com/agawish/Better-Arabic-Reshaper/] 11 | 12 | # Usage: 13 | ### Install python-bidi [https://github.com/MeirKriheli/python-bidi], can be installed from pip `pip install python-bidi`. 14 | 15 | # import arabic_reshaper 16 | # from bidi.algorithm import get_display 17 | # reshaped_text = arabic_reshaper.reshape(u'اللغة العربية رائعة') 18 | # bidi_text = get_display(reshaped_text) 19 | ### Now you can pass `bidi_text` to any function that handles displaying/printing of the text, like writing it to PIL Image or passing it to a PDF generating method. 20 | 21 | import re 22 | 23 | DEFINED_CHARACTERS_ORGINAL_ALF_UPPER_MDD = u'\u0622' 24 | DEFINED_CHARACTERS_ORGINAL_ALF_UPPER_HAMAZA = u'\u0623' 25 | DEFINED_CHARACTERS_ORGINAL_ALF_LOWER_HAMAZA = u'\u0625' 26 | DEFINED_CHARACTERS_ORGINAL_ALF = u'\u0627' 27 | DEFINED_CHARACTERS_ORGINAL_LAM = u'\u0644' 28 | 29 | LAM_ALEF_GLYPHS = [ 30 | [u'\u0622', u'\uFEF6', u'\uFEF5'], 31 | [u'\u0623', u'\uFEF8', u'\uFEF7'], 32 | [u'\u0627', u'\uFEFC', u'\uFEFB'], 33 | [u'\u0625', u'\uFEFA', u'\uFEF9'] 34 | ] 35 | 36 | HARAKAT = [ 37 | u'\u0600', u'\u0601', u'\u0602', u'\u0603', u'\u0606', u'\u0607', u'\u0608', u'\u0609', 38 | u'\u060A', u'\u060B', u'\u060D', u'\u060E', u'\u0610', u'\u0611', u'\u0612', u'\u0613', 39 | u'\u0614', u'\u0615', u'\u0616', u'\u0617', u'\u0618', u'\u0619', u'\u061A', u'\u061B', 40 | u'\u061E', u'\u061F', u'\u0621', u'\u063B', u'\u063C', u'\u063D', u'\u063E', u'\u063F', 41 | u'\u0640', u'\u064B', u'\u064C', u'\u064D', u'\u064E', u'\u064F', u'\u0650', u'\u0651', 42 | u'\u0652', u'\u0653', u'\u0654', u'\u0655', u'\u0656', u'\u0657', u'\u0658', u'\u0659', 43 | u'\u065A', u'\u065B', u'\u065C', u'\u065D', u'\u065E', u'\u0660', u'\u066A', u'\u066B', 44 | u'\u066C', u'\u066F', u'\u0670', u'\u0672', u'\u06D4', u'\u06D5', u'\u06D6', u'\u06D7', 45 | u'\u06D8', u'\u06D9', u'\u06DA', u'\u06DB', u'\u06DC', u'\u06DF', u'\u06E0', u'\u06E1', 46 | u'\u06E2', u'\u06E3', u'\u06E4', u'\u06E5', u'\u06E6', u'\u06E7', u'\u06E8', u'\u06E9', 47 | u'\u06EA', u'\u06EB', u'\u06EC', u'\u06ED', u'\u06EE', u'\u06EF', u'\u06D6', u'\u06D7', 48 | u'\u06D8', u'\u06D9', u'\u06DA', u'\u06DB', u'\u06DC', u'\u06DD', u'\u06DE', u'\u06DF', 49 | u'\u06F0', u'\u06FD', u'\uFE70', u'\uFE71', u'\uFE72', u'\uFE73', u'\uFE74', u'\uFE75', 50 | u'\uFE76', u'\uFE77', u'\uFE78', u'\uFE79', u'\uFE7A', u'\uFE7B', u'\uFE7C', u'\uFE7D', 51 | u'\uFE7E', u'\uFE7F', u'\uFC5E', u'\uFC5F', u'\uFC60', u'\uFC61', u'\uFC62', u'\uFC63' 52 | ] 53 | 54 | ARABIC_GLYPHS = { 55 | u'\u0622' : [u'\u0622', u'\uFE81', u'\uFE81', u'\uFE82', u'\uFE82', 2], 56 | u'\u0623' : [u'\u0623', u'\uFE83', u'\uFE83', u'\uFE84', u'\uFE84', 2], 57 | u'\u0624' : [u'\u0624', u'\uFE85', u'\uFE85', u'\uFE86', u'\uFE86', 2], 58 | u'\u0625' : [u'\u0625', u'\uFE87', u'\uFE87', u'\uFE88', u'\uFE88', 2], 59 | u'\u0626' : [u'\u0626', u'\uFE89', u'\uFE8B', u'\uFE8C', u'\uFE8A', 4], 60 | u'\u0627' : [u'\u0627', u'\u0627', u'\u0627', u'\uFE8E', u'\uFE8E', 2], 61 | u'\u0628' : [u'\u0628', u'\uFE8F', u'\uFE91', u'\uFE92', u'\uFE90', 4], 62 | u'\u0629' : [u'\u0629', u'\uFE93', u'\uFE93', u'\uFE94', u'\uFE94', 2], 63 | u'\u062A' : [u'\u062A', u'\uFE95', u'\uFE97', u'\uFE98', u'\uFE96', 4], 64 | u'\u062B' : [u'\u062B', u'\uFE99', u'\uFE9B', u'\uFE9C', u'\uFE9A', 4], 65 | u'\u062C' : [u'\u062C', u'\uFE9D', u'\uFE9F', u'\uFEA0', u'\uFE9E', 4], 66 | u'\u062D' : [u'\u062D', u'\uFEA1', u'\uFEA3', u'\uFEA4', u'\uFEA2', 4], 67 | u'\u062E' : [u'\u062E', u'\uFEA5', u'\uFEA7', u'\uFEA8', u'\uFEA6', 4], 68 | u'\u062F' : [u'\u062F', u'\uFEA9', u'\uFEA9', u'\uFEAA', u'\uFEAA', 2], 69 | u'\u0630' : [u'\u0630', u'\uFEAB', u'\uFEAB', u'\uFEAC', u'\uFEAC', 2], 70 | u'\u0631' : [u'\u0631', u'\uFEAD', u'\uFEAD', u'\uFEAE', u'\uFEAE', 2], 71 | u'\u0632' : [u'\u0632', u'\uFEAF', u'\uFEAF', u'\uFEB0', u'\uFEB0', 2], 72 | u'\u0633' : [u'\u0633', u'\uFEB1', u'\uFEB3', u'\uFEB4', u'\uFEB2', 4], 73 | u'\u0634' : [u'\u0634', u'\uFEB5', u'\uFEB7', u'\uFEB8', u'\uFEB6', 4], 74 | u'\u0635' : [u'\u0635', u'\uFEB9', u'\uFEBB', u'\uFEBC', u'\uFEBA', 4], 75 | u'\u0636' : [u'\u0636', u'\uFEBD', u'\uFEBF', u'\uFEC0', u'\uFEBE', 4], 76 | u'\u0637' : [u'\u0637', u'\uFEC1', u'\uFEC3', u'\uFEC4', u'\uFEC2', 4], 77 | u'\u0638' : [u'\u0638', u'\uFEC5', u'\uFEC7', u'\uFEC8', u'\uFEC6', 4], 78 | u'\u0639' : [u'\u0639', u'\uFEC9', u'\uFECB', u'\uFECC', u'\uFECA', 4], 79 | u'\u063A' : [u'\u063A', u'\uFECD', u'\uFECF', u'\uFED0', u'\uFECE', 4], 80 | u'\u0641' : [u'\u0641', u'\uFED1', u'\uFED3', u'\uFED4', u'\uFED2', 4], 81 | u'\u0642' : [u'\u0642', u'\uFED5', u'\uFED7', u'\uFED8', u'\uFED6', 4], 82 | u'\u0643' : [u'\u0643', u'\uFED9', u'\uFEDB', u'\uFEDC', u'\uFEDA', 4], 83 | u'\u0644' : [u'\u0644', u'\uFEDD', u'\uFEDF', u'\uFEE0', u'\uFEDE', 4], 84 | u'\u0645' : [u'\u0645', u'\uFEE1', u'\uFEE3', u'\uFEE4', u'\uFEE2', 4], 85 | u'\u0646' : [u'\u0646', u'\uFEE5', u'\uFEE7', u'\uFEE8', u'\uFEE6', 4], 86 | u'\u0647' : [u'\u0647', u'\uFEE9', u'\uFEEB', u'\uFEEC', u'\uFEEA', 4], 87 | u'\u0648' : [u'\u0648', u'\uFEED', u'\uFEED', u'\uFEEE', u'\uFEEE', 2], 88 | u'\u0649' : [u'\u0649', u'\uFEEF', u'\uFEEF', u'\uFEF0', u'\uFEF0', 2], 89 | u'\u0671' : [u'\u0671', u'\u0671', u'\u0671', u'\uFB51', u'\uFB51', 2], 90 | u'\u064A' : [u'\u064A', u'\uFEF1', u'\uFEF3', u'\uFEF4', u'\uFEF2', 4], 91 | u'\u066E' : [u'\u066E', u'\uFBE4', u'\uFBE8', u'\uFBE9', u'\uFBE5', 4], 92 | u'\u06AA' : [u'\u06AA', u'\uFB8E', u'\uFB90', u'\uFB91', u'\uFB8F', 4], 93 | u'\u06C1' : [u'\u06C1', u'\uFBA6', u'\uFBA8', u'\uFBA9', u'\uFBA7', 4], 94 | u'\u06E4' : [u'\u06E4', u'\u06E4', u'\u06E4', u'\u06E4', u'\uFEEE', 2], 95 | u'\u067E' : [u'\u067E', u'\uFB56', u'\uFB58', u'\uFB59', u'\uFB57', 4], 96 | u'\u0698' : [u'\u0698', u'\uFB8A', u'\uFB8A', u'\uFB8A', u'\uFB8B', 2], 97 | u'\u06AF' : [u'\u06AF', u'\uFB92', u'\uFB94', u'\uFB95', u'\uFB93', 4], 98 | u'\u0686' : [u'\u0686', u'\uFB7A', u'\uFB7C', u'\uFB7D', u'\uFB7B', 4], 99 | u'\u06A9' : [u'\u06A9', u'\uFB8E', u'\uFB90', u'\uFB91', u'\uFB8F', 4], 100 | u'\u06CC' : [u'\u06CC', u'\uFEEF', u'\uFEF3', u'\uFEF4', u'\uFEF0', 4] 101 | } 102 | 103 | ARABIC_GLYPHS_LIST = [ 104 | [u'\u0622', u'\uFE81', u'\uFE81', u'\uFE82', u'\uFE82', 2], 105 | [u'\u0623', u'\uFE83', u'\uFE83', u'\uFE84', u'\uFE84', 2], 106 | [u'\u0624', u'\uFE85', u'\uFE85', u'\uFE86', u'\uFE86', 2], 107 | [u'\u0625', u'\uFE87', u'\uFE87', u'\uFE88', u'\uFE88', 2], 108 | [u'\u0626', u'\uFE89', u'\uFE8B', u'\uFE8C', u'\uFE8A', 4], 109 | [u'\u0627', u'\u0627', u'\u0627', u'\uFE8E', u'\uFE8E', 2], 110 | [u'\u0628', u'\uFE8F', u'\uFE91', u'\uFE92', u'\uFE90', 4], 111 | [u'\u0629', u'\uFE93', u'\uFE93', u'\uFE94', u'\uFE94', 2], 112 | [u'\u062A', u'\uFE95', u'\uFE97', u'\uFE98', u'\uFE96', 4], 113 | [u'\u062B', u'\uFE99', u'\uFE9B', u'\uFE9C', u'\uFE9A', 4], 114 | [u'\u062C', u'\uFE9D', u'\uFE9F', u'\uFEA0', u'\uFE9E', 4], 115 | [u'\u062D', u'\uFEA1', u'\uFEA3', u'\uFEA4', u'\uFEA2', 4], 116 | [u'\u062E', u'\uFEA5', u'\uFEA7', u'\uFEA8', u'\uFEA6', 4], 117 | [u'\u062F', u'\uFEA9', u'\uFEA9', u'\uFEAA', u'\uFEAA', 2], 118 | [u'\u0630', u'\uFEAB', u'\uFEAB', u'\uFEAC', u'\uFEAC', 2], 119 | [u'\u0631', u'\uFEAD', u'\uFEAD', u'\uFEAE', u'\uFEAE', 2], 120 | [u'\u0632', u'\uFEAF', u'\uFEAF', u'\uFEB0', u'\uFEB0', 2], 121 | [u'\u0633', u'\uFEB1', u'\uFEB3', u'\uFEB4', u'\uFEB2', 4], 122 | [u'\u0634', u'\uFEB5', u'\uFEB7', u'\uFEB8', u'\uFEB6', 4], 123 | [u'\u0635', u'\uFEB9', u'\uFEBB', u'\uFEBC', u'\uFEBA', 4], 124 | [u'\u0636', u'\uFEBD', u'\uFEBF', u'\uFEC0', u'\uFEBE', 4], 125 | [u'\u0637', u'\uFEC1', u'\uFEC3', u'\uFEC4', u'\uFEC2', 4], 126 | [u'\u0638', u'\uFEC5', u'\uFEC7', u'\uFEC8', u'\uFEC6', 4], 127 | [u'\u0639', u'\uFEC9', u'\uFECB', u'\uFECC', u'\uFECA', 4], 128 | [u'\u063A', u'\uFECD', u'\uFECF', u'\uFED0', u'\uFECE', 4], 129 | [u'\u0641', u'\uFED1', u'\uFED3', u'\uFED4', u'\uFED2', 4], 130 | [u'\u0642', u'\uFED5', u'\uFED7', u'\uFED8', u'\uFED6', 4], 131 | [u'\u0643', u'\uFED9', u'\uFEDB', u'\uFEDC', u'\uFEDA', 4], 132 | [u'\u0644', u'\uFEDD', u'\uFEDF', u'\uFEE0', u'\uFEDE', 4], 133 | [u'\u0645', u'\uFEE1', u'\uFEE3', u'\uFEE4', u'\uFEE2', 4], 134 | [u'\u0646', u'\uFEE5', u'\uFEE7', u'\uFEE8', u'\uFEE6', 4], 135 | [u'\u0647', u'\uFEE9', u'\uFEEB', u'\uFEEC', u'\uFEEA', 4], 136 | [u'\u0648', u'\uFEED', u'\uFEED', u'\uFEEE', u'\uFEEE', 2], 137 | [u'\u0649', u'\uFEEF', u'\uFEEF', u'\uFEF0', u'\uFEF0', 2], 138 | [u'\u0671', u'\u0671', u'\u0671', u'\uFB51', u'\uFB51', 2], 139 | [u'\u064A', u'\uFEF1', u'\uFEF3', u'\uFEF4', u'\uFEF2', 4], 140 | [u'\u066E', u'\uFBE4', u'\uFBE8', u'\uFBE9', u'\uFBE5', 4], 141 | [u'\u06AA', u'\uFB8E', u'\uFB90', u'\uFB91', u'\uFB8F', 4], 142 | [u'\u06C1', u'\uFBA6', u'\uFBA8', u'\uFBA9', u'\uFBA7', 4], 143 | [u'\u067E', u'\uFB56', u'\uFB58', u'\uFB59', u'\uFB57', 4], 144 | [u'\u0698', u'\uFB8A', u'\uFB8A', u'\uFB8A', u'\uFB8B', 2], 145 | [u'\u06AF', u'\uFB92', u'\uFB94', u'\uFB95', u'\uFB93', 4], 146 | [u'\u0686', u'\uFB7A', u'\uFB7C', u'\uFB7D', u'\uFB7B', 4], 147 | [u'\u06A9', u'\uFB8E', u'\uFB90', u'\uFB91', u'\uFB8F', 4], 148 | [u'\u06CC', u'\uFEEF', u'\uFEF3', u'\uFEF4', u'\uFEF0', 4] 149 | ] 150 | 151 | def get_reshaped_glyph(target, location): 152 | if target in ARABIC_GLYPHS: 153 | return ARABIC_GLYPHS[target][location] 154 | else: 155 | return target 156 | 157 | def get_glyph_type(target): 158 | if target in ARABIC_GLYPHS: 159 | return ARABIC_GLYPHS[target][5] 160 | else: 161 | return 2 162 | 163 | def is_haraka(target): 164 | return target in HARAKAT 165 | 166 | def replace_jalalah(unshaped_word): 167 | return re.sub(u'^\u0627\u0644\u0644\u0647$', u'\uFDF2', unshaped_word) 168 | 169 | def replace_lam_alef(unshaped_word): 170 | list_word = list(unshaped_word) 171 | letter_before = u'' 172 | for i in range(len(unshaped_word)): 173 | if not is_haraka(unshaped_word[i]) and unshaped_word[i] != DEFINED_CHARACTERS_ORGINAL_LAM: 174 | letter_before = unshaped_word[i] 175 | 176 | if unshaped_word[i] == DEFINED_CHARACTERS_ORGINAL_LAM: 177 | candidate_lam = unshaped_word[i] 178 | lam_position = i 179 | haraka_position = i + 1 180 | 181 | while haraka_position < len(unshaped_word) and is_haraka(unshaped_word[haraka_position]): 182 | haraka_position += 1 183 | 184 | if haraka_position < len(unshaped_word): 185 | if lam_position > 0 and get_glyph_type(letter_before) > 2: 186 | lam_alef = get_lam_alef(list_word[haraka_position], candidate_lam, False) 187 | else: 188 | lam_alef = get_lam_alef(list_word[haraka_position], candidate_lam, True) 189 | if lam_alef != '': 190 | list_word[lam_position] = lam_alef 191 | list_word[haraka_position] = u' ' 192 | 193 | return u''.join(list_word).replace(u' ', u'') 194 | 195 | def get_lam_alef(candidate_alef, candidate_lam, is_end_of_word): 196 | shift_rate = 1 197 | reshaped_lam_alef = u'' 198 | if is_end_of_word: 199 | shift_rate += 1 200 | 201 | if DEFINED_CHARACTERS_ORGINAL_LAM == candidate_lam: 202 | if DEFINED_CHARACTERS_ORGINAL_ALF_UPPER_MDD == candidate_alef: 203 | reshaped_lam_alef = LAM_ALEF_GLYPHS[0][shift_rate] 204 | 205 | if DEFINED_CHARACTERS_ORGINAL_ALF_UPPER_HAMAZA == candidate_alef: 206 | reshaped_lam_alef = LAM_ALEF_GLYPHS[1][shift_rate] 207 | 208 | if DEFINED_CHARACTERS_ORGINAL_ALF == candidate_alef: 209 | reshaped_lam_alef = LAM_ALEF_GLYPHS[2][shift_rate] 210 | 211 | if DEFINED_CHARACTERS_ORGINAL_ALF_LOWER_HAMAZA == candidate_alef: 212 | reshaped_lam_alef = LAM_ALEF_GLYPHS[3][shift_rate] 213 | 214 | return reshaped_lam_alef 215 | 216 | class DecomposedWord(object): 217 | def __init__(self, word): 218 | self.stripped_harakat = [] 219 | self.harakat_positions = [] 220 | self.stripped_regular_letters = [] 221 | self.letters_position = [] 222 | 223 | for i in range(len(word)): 224 | c = word[i] 225 | if is_haraka(c): 226 | self.harakat_positions.append(i) 227 | self.stripped_harakat.append(c) 228 | else: 229 | self.letters_position.append(i) 230 | self.stripped_regular_letters.append(c) 231 | 232 | def reconstruct_word(self, reshaped_word): 233 | l = list(u'\0' * (len(self.stripped_harakat) + len(reshaped_word))) 234 | for i in range(len(self.letters_position)): 235 | l[self.letters_position[i]] = reshaped_word[i] 236 | for i in range(len(self.harakat_positions)): 237 | l[self.harakat_positions[i]] = self.stripped_harakat[i] 238 | return u''.join(l) 239 | 240 | def get_reshaped_word(unshaped_word): 241 | unshaped_word = replace_jalalah(unshaped_word) 242 | unshaped_word = replace_lam_alef(unshaped_word) 243 | decomposed_word = DecomposedWord(unshaped_word) 244 | result = u'' 245 | if decomposed_word.stripped_regular_letters: 246 | result = reshape_it(u''.join(decomposed_word.stripped_regular_letters)) 247 | return decomposed_word.reconstruct_word(result) 248 | 249 | def reshape_it(unshaped_word): 250 | if not unshaped_word: 251 | return u'' 252 | if len(unshaped_word) == 1: 253 | return get_reshaped_glyph(unshaped_word[0], 1) 254 | reshaped_word = [] 255 | for i in range(len(unshaped_word)): 256 | before = False 257 | after = False 258 | if i == 0: 259 | after = get_glyph_type(unshaped_word[i]) == 4 260 | elif i == len(unshaped_word) - 1: 261 | before = get_glyph_type(unshaped_word[i - 1]) == 4 262 | else: 263 | after = get_glyph_type(unshaped_word[i]) == 4 264 | before = get_glyph_type(unshaped_word[i - 1]) == 4 265 | if after and before: 266 | reshaped_word.append(get_reshaped_glyph(unshaped_word[i], 3)) 267 | elif after and not before: 268 | reshaped_word.append(get_reshaped_glyph(unshaped_word[i], 2)) 269 | elif not after and before: 270 | reshaped_word.append(get_reshaped_glyph(unshaped_word[i], 4)) 271 | elif not after and not before: 272 | reshaped_word.append(get_reshaped_glyph(unshaped_word[i], 1)) 273 | 274 | return u''.join(reshaped_word) 275 | 276 | 277 | def is_arabic_character(target): 278 | return target in ARABIC_GLYPHS or target in HARAKAT 279 | 280 | def get_words(sentence): 281 | if sentence: 282 | return re.split('\\s', sentence) 283 | return [] 284 | 285 | def has_arabic_letters(word): 286 | for c in word: 287 | if is_arabic_character(c): 288 | return True 289 | return False 290 | 291 | def is_arabic_word(word): 292 | for c in word: 293 | if not is_arabic_character(c): 294 | return False 295 | return True 296 | 297 | def get_words_from_mixed_word(word): 298 | temp_word = u'' 299 | words = [] 300 | for c in word: 301 | if is_arabic_character(c): 302 | if temp_word and not is_arabic_word(temp_word): 303 | words.append(temp_word) 304 | temp_word = c 305 | else: 306 | temp_word += c 307 | else: 308 | if temp_word and is_arabic_word(temp_word): 309 | words.append(temp_word) 310 | temp_word = c 311 | else: 312 | temp_word += c 313 | if temp_word: 314 | words.append(temp_word) 315 | return words 316 | 317 | def reshape(text): 318 | if text: 319 | lines = re.split('\\r?\\n', text) 320 | for i in range(len(lines)): 321 | lines[i] = reshape_sentence(lines[i]) 322 | return u'\n'.join(lines) 323 | return u'' 324 | 325 | def reshape_sentence(sentence): 326 | words = get_words(sentence) 327 | for i in range(len(words)): 328 | word = words[i] 329 | if has_arabic_letters(word): 330 | if is_arabic_word(word): 331 | words[i] = get_reshaped_word(word) 332 | else: 333 | mixed_words = get_words_from_mixed_word(word) 334 | for j in range(len(mixed_words)): 335 | mixed_words[j] = get_reshaped_word(mixed_words[j]) 336 | words[i] = u''.join(mixed_words) 337 | return u' '.join(words) 338 | -------------------------------------------------------------------------------- /data/fonts/Mirta.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/data/fonts/Mirta.ttf -------------------------------------------------------------------------------- /data/images/h_line.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/data/images/h_line.png -------------------------------------------------------------------------------- /data/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/data/images/loading.gif -------------------------------------------------------------------------------- /images/estates/0f29e97a-8aa3-4c35-860f-6f2796d6bf8d-1410212873_home-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/images/estates/0f29e97a-8aa3-4c35-860f-6f2796d6bf8d-1410212873_home-512.png -------------------------------------------------------------------------------- /images/estates/55000ff9-28ae-4f72-8b97-96c892c9ceac-margaret_mitchell_house_atlanta_2006.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/images/estates/55000ff9-28ae-4f72-8b97-96c892c9ceac-margaret_mitchell_house_atlanta_2006.jpg -------------------------------------------------------------------------------- /images/estates/5c4002f2-411b-4044-a853-a1f5773281a7-knowledge.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/images/estates/5c4002f2-411b-4044-a853-a1f5773281a7-knowledge.jpg -------------------------------------------------------------------------------- /images/estates/5e9cc024-d4fe-4807-b49c-e5b977324be0-1413076197_679595-camera-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/images/estates/5e9cc024-d4fe-4807-b49c-e5b977324be0-1413076197_679595-camera-512.png -------------------------------------------------------------------------------- /images/estates/81dc7988-8c7a-4d25-8eff-cee9b38fb96a-1413076197_679595-camera-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/images/estates/81dc7988-8c7a-4d25-8eff-cee9b38fb96a-1413076197_679595-camera-512.png -------------------------------------------------------------------------------- /images/estates/ee50eff9-6f76-4aa3-8f33-18285b9312ef-1410212873_home-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/images/estates/ee50eff9-6f76-4aa3-8f33-18285b9312ef-1410212873_home-512.png -------------------------------------------------------------------------------- /images/services/0f29e97a-8aa3-4c35-860f-6f2796d6bf8d-1410212873_home-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/images/services/0f29e97a-8aa3-4c35-860f-6f2796d6bf8d-1410212873_home-512.png -------------------------------------------------------------------------------- /images/services/5e9cc024-d4fe-4807-b49c-e5b977324be0-1413076197_679595-camera-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/images/services/5e9cc024-d4fe-4807-b49c-e5b977324be0-1413076197_679595-camera-512.png -------------------------------------------------------------------------------- /images/services/ee50eff9-6f76-4aa3-8f33-18285b9312ef-1410212873_home-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/images/services/ee50eff9-6f76-4aa3-8f33-18285b9312ef-1410212873_home-512.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # _*_ coding: UTF8 _*_ 2 | import kivy 3 | from Controller.feature import FeatureOperation 4 | from kivy.uix.popup import Popup 5 | from kivy.uix.image import Image 6 | from kivy.uix.scrollview import ScrollView 7 | kivy.require('1.10.0') 8 | 9 | from kivy.app import App 10 | from kivy.uix.boxlayout import BoxLayout 11 | from kivy.uix.label import Label 12 | from kivy.uix.gridlayout import GridLayout 13 | from kivy.uix.accordion import AccordionItem 14 | from requests import get, ConnectionError 15 | from kivy.properties import (ObjectProperty, StringProperty, NumericProperty, ListProperty) 16 | from kivy.network.urlrequest import UrlRequest 17 | from kivy.factory import Factory 18 | from Model import estateViewModel, initial_db 19 | from bidi.algorithm import get_display 20 | import arabic_reshaper 21 | #from Model import store, get 22 | from Controller.estateviewmodel import EstateOperation 23 | 24 | base_url = ''.join([estateViewModel.WEBSITE, '/real-estate/?page={}']) 25 | detail_url = '/'.join([estateViewModel.WEBSITE, 'real-estate/{}']) 26 | PAGE_SIZE = 4.0 27 | MAXHEIGHT = 768 28 | MELKITEMHEIGHT = 100 29 | STRINGEMPTY = '' 30 | estate_operation = EstateOperation() 31 | feature_operation = FeatureOperation() 32 | 33 | 34 | def set_shape(text): 35 | 36 | if isinstance(text, str): 37 | text = unicode(text, 'utf-8') 38 | 39 | reshaped_text = arabic_reshaper.reshape(text) 40 | return get_display(reshaped_text) 41 | 42 | class Loading(BoxLayout): 43 | 44 | def __init__(self, **kwargs): 45 | super(Loading, self).__init__(**kwargs) 46 | self.padding = '7dp' 47 | self.orientation = 'vertical' 48 | loading_image = Image(source = 'data/images/loading.gif') 49 | self.add_widget(loading_image) 50 | 51 | LOADING = Popup(title = 'Loading ...', content = Loading(), size_hint = (None, None), size = ('169dp', '144dp')) 52 | 53 | def restore_data(url, success_func, failure_func): 54 | progress_start() 55 | try: 56 | response = get(url) 57 | if response.status_code == 200: 58 | success_func(response.json()) 59 | else: 60 | failure_func([]) 61 | except ConnectionError: 62 | failure_func([]) 63 | progress_end() 64 | 65 | def progress_start(): 66 | LOADING.open() 67 | 68 | def progress_end(): 69 | LOADING.dismiss() 70 | 71 | class UnicodeLabel(Label): 72 | def __init__(self, **kwargs): 73 | self.font_name = 'data/fonts/Mirta.ttf' 74 | self.markup = True 75 | self.color = (1, 0, 0.96, 1) 76 | super(UnicodeLabel, self).__init__() 77 | 78 | def set_shape(self, text): 79 | 80 | if isinstance(text, str): 81 | text = unicode(text, 'utf-8') 82 | 83 | reshaped_text = arabic_reshaper.reshape(text) 84 | return get_display(reshaped_text) 85 | 86 | # RuntimeError: maximum recursion depth exceeded 87 | # def on_text(self, *args): 88 | # self.text = self.set_shape(args[1]) 89 | 90 | class Index(BoxLayout): 91 | estates = ObjectProperty() 92 | scroll_view = ObjectProperty() 93 | page = 1 94 | count = 0 95 | lock = False 96 | 97 | def search_operation(self, url = base_url): 98 | self.lock = True 99 | restore_data(url, self.restore_data, self.restore_local_data) 100 | 101 | def restore_data(self, data): 102 | # if first UrlRequest failure and after restore_local_data 103 | # UrlRequest success, all previous data cleared from screen 104 | if self.page is 1: 105 | self.estates.clear_widgets() 106 | 107 | self.get_data(data) 108 | if len(data): 109 | scroll_y = (1.0/self.page)*(len(data)/PAGE_SIZE) 110 | self.count += len(data) 111 | 112 | # set scroll to point that data added 113 | self.scroll_view.scroll_y = scroll_y 114 | self.page += 1 115 | 116 | self.lock = False 117 | if len(data) and (MAXHEIGHT >= (self.count * MELKITEMHEIGHT)): 118 | url = base_url.format(self.page) 119 | self.search_operation(url) 120 | 121 | def get_data(self, data): 122 | estate_list = estate_operation.add(data) 123 | if estate_list: 124 | self.set_melk_items(estate_list) 125 | else: 126 | self.restore_local_data() 127 | 128 | 129 | def set_melk_items(self, estate_list): 130 | for estate in estate_list: 131 | melk_item = MelkItem() 132 | self.set_melk_item(melk_item, estate) 133 | self.estates.add_widget(melk_item) 134 | 135 | def restore_local_data(self, *args): 136 | if not self.estates.children: 137 | estate_list = estate_operation.index() 138 | self.set_melk_items(estate_list) 139 | self.lock = False 140 | 141 | def set_melk_item(self, melk_item, melk): 142 | """ 143 | @type melk: EstateViewModel 144 | """ 145 | melk_item.melk_id = melk.id 146 | melk_item.area = melk.get_area() 147 | melk_item.price = melk.get_price() 148 | melk_item.type = melk.get_estate_type() 149 | melk_item.image_url = melk.get_image_url() 150 | 151 | def scrolling(self, scroll_y): 152 | if scroll_y <= 0.0 and self.lock is False: 153 | url = base_url.format(self.page) 154 | self.search_operation(url) 155 | 156 | 157 | class MelkItem(BoxLayout): 158 | melk_id = NumericProperty() 159 | type = StringProperty() 160 | area = StringProperty() 161 | price = StringProperty() 162 | image_url = StringProperty() 163 | background_color = ListProperty([1, 1, 1]) 164 | 165 | def on_touch_down(self, touch): 166 | if self.collide_point(touch.x, touch.y): 167 | self.background_color = [0.95, 0.95, 0.95] 168 | return super(MelkItem, self).on_touch_down(touch) 169 | 170 | def on_touch_move(self, touch): 171 | touch.ud['melk_item'] = True 172 | self.background_color = [1, 1, 1] 173 | super(MelkItem, self).on_touch_move(touch) 174 | 175 | def on_touch_up(self, touch): 176 | # if touch moved don't response to them just touch up 177 | if touch.ud.get('melk_item') is None: 178 | if self.collide_point(touch.x, touch.y): 179 | self.background_color = [1, 1, 1] 180 | self.show() 181 | else: 182 | touch.ud['melk_item'] = False 183 | super(MelkItem, self).on_touch_up(touch) 184 | 185 | def show(self): 186 | app = MelkApp.get_running_app() 187 | app.root.show(self.melk_id) 188 | 189 | class Melk(BoxLayout): 190 | pass 191 | 192 | class RealEstateContainer(BoxLayout): 193 | index_page = ObjectProperty() 194 | melk_page = ObjectProperty(None) 195 | container_view = ObjectProperty() 196 | 197 | def __init__(self, **kwargs): 198 | self.font_name = 'data/fonts/Mirta.ttf' 199 | super(RealEstateContainer, self).__init__(**kwargs) 200 | self.index_page.search_operation() 201 | 202 | def show(self, melk_id): 203 | url = detail_url.format(melk_id) 204 | self.melk_page = Melk() 205 | self.feature = feature_operation[melk_id] 206 | if self.feature is None: 207 | restore_data(url, self.get_melk, self.get_melk) 208 | if self.feature is not None: 209 | self.show_melk() 210 | self.container_view.clear_widgets() 211 | self.container_view.add_widget(self.melk_page) 212 | 213 | def show_latest_view_melk(self): 214 | if self.melk_page is not None: 215 | self.container_view.clear_widgets() 216 | self.container_view.add_widget(self.melk_page) 217 | 218 | def get_melk(self, data): 219 | if data: 220 | data = data[0] 221 | self.feature = feature_operation.add(data) 222 | 223 | def show_index(self): 224 | self.container_view.clear_widgets() 225 | self.container_view.add_widget(self.index_page) 226 | 227 | def show_melk(self): 228 | _ = self.feature 229 | feature = self.melk_page.ids.feature 230 | feature.area = _.area 231 | feature.built_up_area = _.built_up_area 232 | feature.construction_date = _.construction_date 233 | feature.control = str(_.control) 234 | feature.document_situation = str(_.document) 235 | feature.estate_position = str(_.position) 236 | feature.estate_type = str(_.type) 237 | feature.estate_priority = str(_.priority) 238 | feature.description = _.description or STRINGEMPTY 239 | feature.is_sold = _.is_sold 240 | 241 | if _.home: 242 | home = self.melk_page.ids.home 243 | home.frame_type = str(_.home.frame_type) 244 | home.number_of_floors = _.home.floor 245 | home.floor_for_sell_or_rent = _.home.the_floor 246 | home.unit_for_sell_or_rent = _.home.unit 247 | 248 | if _.experience: 249 | experience = self.melk_page.ids.experience 250 | experience.water = _.experience.water 251 | experience.power = _.experience.power 252 | experience.phone = _.experience.phone 253 | experience.gas = _.experience.gas 254 | 255 | if _.facilities: 256 | facilities = self.melk_page.ids.facilities 257 | facilities.number_of_bedrooms = _.facilities.bedroom 258 | facilities.heating_system = _.facilities.heating_system 259 | facilities.cooling_system = _.facilities.cooling_system 260 | facilities.well = _.facilities.well 261 | facilities.warehouse = _.facilities.warehouse 262 | facilities.balcony = _.facilities.balcony 263 | facilities.basement = _.facilities.basement 264 | facilities.elevator = _.facilities.elevator 265 | facilities.shooting = _.facilities.shooting 266 | 267 | if _.services: 268 | service = self.melk_page.ids.service 269 | for item in _.services: 270 | serv = AccordionItem(title=str(item.type)) 271 | 272 | srv = Factory.Service() 273 | srv.description = item.description or STRINGEMPTY 274 | 275 | for image in item.images: 276 | __ = Factory.EstateImage() 277 | __.is_default = image.is_default 278 | __.file_name = image.file_name 279 | srv.add_widget(__) 280 | 281 | serv.add_widget(srv) 282 | service.add_widget(serv) 283 | 284 | description = self.melk_page.ids.description 285 | description.description = feature.description 286 | 287 | if _.address: 288 | address = self.melk_page.ids.address 289 | address.country = _.address.country or STRINGEMPTY 290 | address.province = _.address.province or STRINGEMPTY 291 | address.city = _.address.city or STRINGEMPTY 292 | address.region = _.address.region or STRINGEMPTY 293 | address.address = _.address.addr or STRINGEMPTY 294 | address.postal_code = _.address.postal_code or STRINGEMPTY 295 | 296 | if _.cost: 297 | cost = self.melk_page.ids.cost 298 | cost.cost_metric = _.cost.cost_metric 299 | cost.price = _.cost.price 300 | 301 | if _.user: 302 | user = self.melk_page.ids.user 303 | user.first_name = _.user.first_name or STRINGEMPTY 304 | user.last_name = _.user.last_name or STRINGEMPTY 305 | user.mobile_number = ''.join(['09', str(_.user.username)]) or STRINGEMPTY 306 | user.phone_number = ''.join(['0', str(_.user.city_code), ' - ', str(_.user.tel)]) or STRINGEMPTY 307 | 308 | class DetailGrid(GridLayout): 309 | def __init__(self, **kwargs): 310 | super(DetailGrid, self).__init__(**kwargs) 311 | self.bind(minimum_height=self.setter('height')) 312 | 313 | class ScrollContainer(GridLayout): 314 | def __init__(self, **kwargs): 315 | super(ScrollContainer, self).__init__(**kwargs) 316 | self.bind(minimum_height=self.setter('height')) 317 | 318 | class MelkApp(App): 319 | pass 320 | 321 | if __name__ == '__main__': 322 | # Initialize database for models 323 | initial_db() 324 | MelkApp().run() -------------------------------------------------------------------------------- /melk.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/python-programmer/kivy-simple-mvc-template/69e64c7edfdb4a94c0dcf2268b634640cde1f638/melk.db -------------------------------------------------------------------------------- /melk.kv: -------------------------------------------------------------------------------- 1 | # _*_ coding: UTF8 _*_ 2 | #: import set_shape main.set_shape 3 | RealEstateContainer: 4 | 5 | : 6 | container_view: container_id 7 | index_page: index_id 8 | orientation: 'vertical' 9 | canvas: 10 | Color: 11 | rgb: [1.0, 0.84, 0] 12 | Rectangle: 13 | size: self.size 14 | pos: self.pos 15 | ActionBar: 16 | ActionView: 17 | use_separator: True 18 | ActionPrevious: 19 | text: 'Melk-1' 20 | with_previous: False 21 | ActionButton: 22 | text: set_shape('کل املاک') 23 | font_name: 'data/fonts/Mirta.ttf' 24 | on_press: app.root.show_index() 25 | ActionButton: 26 | text: set_shape('ملک آخر') 27 | font_name: 'data/fonts/Mirta.ttf' 28 | on_press: app.root.show_latest_view_melk() 29 | 30 | BoxLayout: 31 | id: container_id 32 | orientation: 'vertical' 33 | Index: 34 | id: index_id 35 | 36 | 37 | : 38 | orientation: 'vertical' 39 | estates: estate_list 40 | scroll_view: scrollveiw_id 41 | ScrollView: 42 | id: scrollveiw_id 43 | bar_margin: 10 44 | update_from_scroll: root.scrolling(self.scroll_y) 45 | ScrollContainer: 46 | id: estate_list 47 | 48 | : 49 | orientation: 'vertical' 50 | padding: '5dp' 51 | size_hint_y: None 52 | height: '100dp' 53 | canvas: 54 | Color: 55 | rgb: root.background_color 56 | Rectangle: 57 | size: self.size 58 | pos: self.pos 59 | BoxLayout: 60 | BoxLayout: 61 | size_hint_x: 30 62 | padding: '3dp' 63 | AsyncImage: 64 | source: root.image_url 65 | GridLayout: 66 | size_hint_x: 70 67 | cols: 1 68 | spacing: '3dp' 69 | UnicodeLabel: 70 | text: self.set_shape(root.type) 71 | HLine: 72 | UnicodeLabel: 73 | text: self.set_shape(root.area) 74 | HLine: 75 | UnicodeLabel: 76 | text: self.set_shape(root.price) 77 | 78 | 79 | : 80 | source: 'data/images/h_line.png' 81 | 82 | : 83 | padding: '1dp' 84 | orientation: 'vertical' 85 | feature_grid: features_id 86 | 87 | ScrollView: 88 | bar_margin: 10 89 | 90 | ScrollContainer: 91 | id: features_id 92 | 93 | ## Feature 94 | Feature: 95 | id: feature 96 | 97 | ## Home 98 | Home: 99 | id: home 100 | 101 | ## Experience 102 | Experience: 103 | id: experience 104 | 105 | ## Facilities 106 | Facilities: 107 | id: facilities 108 | 109 | ## Services 110 | Accordion: 111 | orientation: 'vertical' 112 | size_hint_y: None 113 | height: '400dp' 114 | id: service 115 | 116 | ## Extra comment 117 | Description: 118 | id: description 119 | 120 | ## Address 121 | Address: 122 | id: address 123 | 124 | ## Cost 125 | Cost: 126 | id: cost 127 | 128 | ## User 129 | User: 130 | id: user 131 | 132 | : 133 | size_hint_y: None 134 | height: '40dp' 135 | color: 0, 0, 0 136 | canvas.before: 137 | Color: 138 | rgb: 1.0, 1.0, 1.0 139 | Rectangle: 140 | pos: self.pos 141 | size: self.size 142 | 143 | : 144 | cols: 2 145 | spacing: '1dp' 146 | padding: '2dp' 147 | size_hint_y: None 148 | 149 | : 150 | cols: 1 151 | spacing: '2dp' 152 | size_hint_y:None 153 | padding: '5dp' 154 | 155 | : 156 | area: 0 157 | built_up_area: 0 158 | construction_date: 1300 159 | is_sold: False 160 | control: '' 161 | document_situation: '' 162 | estate_position: '' 163 | estate_type: '' 164 | estate_priority: '' 165 | description: '' 166 | 167 | DetailLabel: 168 | text: self.set_shape(root.estate_type) 169 | size_hint_x: 60 170 | DetailLabel: 171 | text: self.set_shape('نوع ملک') 172 | size_hint_x: 40 173 | 174 | DetailLabel: 175 | text: self.set_shape(root.document_situation) 176 | DetailLabel: 177 | text: self.set_shape('وضعیت سند') 178 | 179 | DetailLabel: 180 | text: self.set_shape(root.estate_position) 181 | DetailLabel: 182 | text: self.set_shape('موقعیت ملک') 183 | 184 | DetailLabel: 185 | text: self.set_shape(root.control) 186 | DetailLabel: 187 | text: self.set_shape('کاربری') 188 | 189 | DetailLabel: 190 | text: str(root.area) 191 | DetailLabel: 192 | text: self.set_shape('مساحت') 193 | 194 | DetailLabel: 195 | text: str(root.built_up_area) 196 | DetailLabel: 197 | text: self.set_shape('متراژ زیربنا') 198 | 199 | DetailLabel: 200 | text: str(root.construction_date) 201 | DetailLabel: 202 | text: self.set_shape('سال ساخت') 203 | 204 | : 205 | frame_type: '' 206 | number_of_floors: 0 207 | floor_for_sell_or_rent: 0 208 | unit_for_sell_or_rent: 0 209 | 210 | DetailLabel: 211 | text: self.set_shape(root.frame_type) 212 | size_hint_x: 60 213 | DetailLabel: 214 | text: self.set_shape('نوع اسکلت بندی') 215 | size_hint_x: 40 216 | 217 | DetailLabel: 218 | text: str(root.number_of_floors) 219 | DetailLabel: 220 | text: self.set_shape('تعداد طبقات') 221 | 222 | DetailLabel: 223 | text: str(root.floor_for_sell_or_rent) 224 | DetailLabel: 225 | text: self.set_shape('طبقه ی مورد نظر') 226 | 227 | DetailLabel: 228 | text: str(root.unit_for_sell_or_rent) 229 | DetailLabel: 230 | text: self.set_shape('واحد مورد نظر') 231 | 232 | : 233 | water: False 234 | power: False 235 | phone: False 236 | gas: False 237 | 238 | DetailLabel: 239 | text: str(root.water) 240 | size_hint_x: 60 241 | DetailLabel: 242 | text: self.set_shape('آب') 243 | size_hint_x: 40 244 | 245 | DetailLabel: 246 | text: str(root.power) 247 | DetailLabel: 248 | text: self.set_shape('برق') 249 | 250 | DetailLabel: 251 | text: str(root.phone) 252 | DetailLabel: 253 | text: self.set_shape('تلفن') 254 | 255 | DetailLabel: 256 | text: str(root.gas) 257 | DetailLabel: 258 | text: self.set_shape('گاز') 259 | 260 | : 261 | number_of_bedrooms: 0 262 | heating_system: False 263 | cooling_system: False 264 | well: False 265 | warehouse: False 266 | balcony: False 267 | basement: False 268 | elevator: False 269 | shooting: False 270 | 271 | DetailLabel: 272 | text: str(root.number_of_bedrooms) 273 | size_hint_x: 60 274 | DetailLabel: 275 | text: self.set_shape('تعداد اتاق خواب') 276 | size_hint_x: 40 277 | 278 | DetailLabel: 279 | text: str(root.heating_system) 280 | DetailLabel: 281 | text: self.set_shape('سیستم گرمایشی') 282 | 283 | DetailLabel: 284 | text: str(root.cooling_system) 285 | DetailLabel: 286 | text: self.set_shape('سیستم سرمایشی') 287 | 288 | DetailLabel: 289 | text: str(root.well) 290 | DetailLabel: 291 | text: self.set_shape('چاه') 292 | 293 | DetailLabel: 294 | text: str(root.warehouse) 295 | DetailLabel: 296 | text: self.set_shape('انبار') 297 | 298 | DetailLabel: 299 | text: str(root.basement) 300 | DetailLabel: 301 | text: self.set_shape('زیرزمین') 302 | 303 | DetailLabel: 304 | text: str(root.elevator) 305 | DetailLabel: 306 | text: self.set_shape('آسانسور') 307 | 308 | DetailLabel: 309 | text: str(root.balcony) 310 | DetailLabel: 311 | text: self.set_shape('بالکن') 312 | 313 | DetailLabel: 314 | text: str(root.shooting) 315 | DetailLabel: 316 | text: self.set_shape('شوتینگ زباله') 317 | 318 | : 319 | country: '' 320 | province: '' 321 | city: '' 322 | region: '' 323 | postal_code: '' 324 | address: '' 325 | 326 | DetailLabel: 327 | text: self.set_shape(root.country) 328 | size_hint_x: 60 329 | DetailLabel: 330 | text: self.set_shape('کشور') 331 | size_hint_x: 40 332 | 333 | DetailLabel: 334 | text: self.set_shape(root.province) 335 | DetailLabel: 336 | text: self.set_shape('استان') 337 | 338 | DetailLabel: 339 | text: self.set_shape(root.city) 340 | DetailLabel: 341 | text: self.set_shape('شهر') 342 | 343 | DetailLabel: 344 | text: self.set_shape(root.region) 345 | DetailLabel: 346 | text: self.set_shape('منطقه') 347 | 348 | DetailLabel: 349 | text: self.set_shape(root.postal_code) 350 | DetailLabel: 351 | text: self.set_shape('کد پستی') 352 | 353 | DetailLabel: 354 | text: self.set_shape(root.address) 355 | DetailLabel: 356 | text: self.set_shape('آدرس') 357 | 358 | : 359 | cost_metric: 0 360 | price: 0 361 | 362 | DetailLabel: 363 | text: str(root.cost_metric) 364 | size_hint_x: 60 365 | DetailLabel: 366 | text: self.set_shape('نرخ متری') 367 | size_hint_x: 40 368 | 369 | DetailLabel: 370 | text: str(root.price) 371 | DetailLabel: 372 | text: self.set_shape('قیمت') 373 | 374 | : 375 | first_name: '' 376 | last_name: '' 377 | mobile_number: '' 378 | phone_number: '' 379 | 380 | DetailLabel: 381 | text: self.set_shape(', '.join([root.first_name, root.last_name])) 382 | size_hint_x: 60 383 | DetailLabel: 384 | text: self.set_shape('نام') 385 | size_hint_x: 40 386 | 387 | DetailLabel: 388 | text: self.set_shape(root.mobile_number) 389 | DetailLabel: 390 | text: self.set_shape('شماره موبایل') 391 | 392 | DetailLabel: 393 | text: self.set_shape(root.phone_number) 394 | DetailLabel: 395 | text: self.set_shape('شماره ثابت') 396 | 397 | : 398 | description: '' 399 | 400 | DetailLabel: 401 | text: self.set_shape(root.description) 402 | 403 | : 404 | description: '' 405 | orientation: 'vertical' 406 | padding: '7dp' 407 | 408 | DetailLabel: 409 | text: self.set_shape(root.description) 410 | 411 | : 412 | orientation: 'vertical' 413 | file_name: '' 414 | is_default: False 415 | size_hint_y: 50 416 | 417 | AsyncImage: 418 | source: root.file_name 419 | size_hint: (None, None) 420 | height: '256dp' 421 | width: '200dp' 422 | 423 | DetailLabel: 424 | text: ' '.join([str(root.is_default), self.set_shape('به عنوان عکس پیش فرض')]) 425 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pillow==4.2.1 2 | pygame==1.9.3 3 | python-bidi==0.4.0 4 | peewee==2.10.1 --------------------------------------------------------------------------------