📜 ⬆️ ⬇️

A bunch of ExtJS + Django + Apache + SVN deploy (and a simple CRUD controller on Django)

Foreword

I just want to apologize for such an overloaded article, but for me now all this is relevant and connected. I think that some of this may be useful for future development. I want to note that in this article I will not tell you how to install certain trivial things, the installation of which, moreover, depends on a particular platform. Also in the article I do not describe the gestures on setting up access rights to the server files, again, this depends on the implementation. The article describes the configuration process on the PDC server named tci.lan, all the names are saved, in your case they should be replaced with the ones you need. This article contains the code, to improve readability it is hidden in spoilers.

Formulation of the problem
Recently, I faced the task: to write a simple database for the organization in which I work. In principle, I was given the opportunity to choose the architecture, storage, framework, etc. The technical task was also provided very ambiguously - in the form of a list of all attributes of all models (there was no division into models).

Choice of architecture
In the choice of architecture, I was operating in that the system should be cross-platform and, preferably, does not require additional software installation (frameworks, Vine, flash, silverlight, etc.). Based on this, I stopped at a web application with a client-server architecture. Actually, this also contributed to the presence in the organization of a web server on CentOS, which I also administer.
As a GUI chose ExtJS. At the time of development, the latest version was release 4.2b. As a backend, if you can call it that way (it’s rather an API server), I chose Django, since I’ve come across it and wanted to get to know it better.
I chose PyCharm as the IDE, which seems to be one of the most normal IDEs for Python.
')
Result
The result is a customized system for development on ExtJS and Django:


Setting in order

Apache web server setup
So let's start with Apache. We assume that the web server is already installed. First we need a virtual host (at least for those who have more than one site hosted on the server).
To create a virtual host, you only need to create the file /etc/httpd/conf/vhosts/db.tci.lan.conf with approximately
following content:
 <VirtualHost *:80> ServerAdmin lufton@gmail.com ServerName www.db.tci.lan ServerAlias db.tci.lan DirectoryIndex index.html index.php DocumentRoot /home/lufton/public_html/db.tci.lan/public WSGIScriptAlias / /home/lufton/public_html/db.tci.lan/public/db/mod.wsgi Alias /js/app.js "/home/lufton/public_html/db.tci.lan/public/db/app.js" Alias /css "/home/lufton/public_html/db.tci.lan/public/db/css" <Location "/css"> SetHandler None Allow from all </Location> Alias /js "/home/lufton/public_html/db.tci.lan/public/db/js" <Location "/js"> SetHandler None Allow from all </Location> Alias /img "/home/lufton/public_html/db.tci.lan/public/db/img" <Location "/img"> SetHandler None Allow from all </Location> Alias /media "/usr/lib/python2.6/site-packages/django/contrib/admin/media" <Location "/media"> SetHandler None Allow from all </Location> <Location "/svnmanager"> SetHandler None Allow from all </Location> LogLevel warn ErrorLog /home/lufton/public_html/db.tci.lan/log/error.log CustomLog /home/lufton/public_html/db.tci.lan/log/access.log combined </VirtualHost> LoadModule python_module modules/mod_python.so <Directory /home/lufton/public_html/db.tci.lan/> Options Indexes FollowSymLinks MultiViews AllowOverride None Order allow,deny allow from all AddHandler mod_python .py PythonHandler mod_python.publisher | .py AddHandler mod_python .psp .psp_ PythonHandler mod_python.psp | .psp .psp_ PythonDebug On </Directory> 


This file sets up the root folder of the server files, sets the processing of *.py files by Python and creates 5 alias ( /js/app.js , /img , /css , /js , /media ) to serve the static ExtJS files and Django Admin. Also adds the project path to the Python system path.
Here lufton is the user name, db.tci.lan is the address where our server will be available. You also need to take care that the files from the /etc/httpd/conf/vhosts/ folder are included in the config file. To do this, add / uncomment the line in the /etc/httpd/conf/httpd.conf file:
 Include /etc/httpd/conf/vhosts/*.conf 

Make sure also that you have installed mod_wsgi and it is monitored in this file.
It is also necessary to create a structure in the appropriate folder:

This completes the Apache configuration.

Subversion setup
First you need to create a repository, in my case it is called db located along the path /srv/svn/repos/db . After the repository is created, it is necessary to configure the SVN so that after each commit, the repository HEAD files are updated in the root folder of the server. To do this, first of all you need to checkout the repository in the root folder of the server. This is done as usual.
svn checkout 127.0.0.1/svn/db /home/lufton/public_html/db.tci.lan/public/db
Now you need to copy the file from
/srv/svn/repos/db/hooks/post-commit.tmpl
at
/srv/svn/repos/db/hooks/post-commit
Instead of mailer.py commit "$REPOS" "$REV" /path/to/mailer.conf add the lines:
 cd /home/lufton/public_html/db.tci.lan/public/db /usr/bin/svn update # python manage.py syncdb # /etc/init.d/httpd restart 

Now, after each commit'a in the repository, the folder will be updated automatically, which will reduce the number of your gestures actions.

Creating a simple CRUD controller
In the task set before me, a large number of actions with records in the database was foreshadowed, so work with models should be simplified to a minimum, and the functionality should be preserved. My implementation allows:


Model class inheritance
So let's start with the creation of an abstract class inherited from Model. Each model should now be able to present itself as a JSON-like structure. For convenience, I also added some useful methods. I’ll say right away that it’s far from Python, so the solution may not be the most elegant, but still working.
Class text:
 class CoolModel ( Model ): class Meta: abstract = True app_label = "db" def __init__ ( self, *args, **kwargs ): super(CoolModel, self).__init__(*args, **kwargs) self.__initial = self._dict def toDict ( self, properties = "*" ): def getValue ( field, properties = "*" ): value = getattr(self, field.name) if isinstance(field, ForeignKey): if field.name in properties: return value.toDict(properties[field.name]) elif isinstance(value, datetime.date) or isinstance(value, datetime.datetime): return value.isoformat() elif isinstance(field, CommaSeparatedIntegerField) and isinstance(value, basestring): return json.loads(value) elif isinstance(value, Decimal): return float(value) elif isinstance(field, ImageField): return value.url if value else None elif isinstance(field, NullBooleanField): return None if value not in (True, False) else 1 if value else 0 else: return value result = {} fields = {} for field in self._meta.fields: fields[field.name] = field if isinstance(field, ForeignKey): idAttr = "%s_id" % field.name result[idAttr] = getattr(self, idAttr) else: result[field.name] = getValue(field, properties) if isinstance(properties, dict): for k, v in properties.iteritems(): if hasattr(self, k): value = getattr(self, k) if isinstance(value, CoolModel): result[k] = value.toDict(v) elif value.__class__.__name__ == "RelatedManager": result[k] = toJSON(value.all(), v) elif value is None: result[k] = {} if k in fields and isinstance(fields[k], ForeignKey) else None else: result[k] = value return result @property def diff ( self ): d1 = self.__initial d2 = self._dict diffs = [(k, (v, d2[k])) for k, v in d1.items() if v != d2[k]] return dict(diffs) @property def original ( self ): try: return self.__class__.objects.get(id = self.id) except self.__class__.DoesNotExist: return None @property def hasChanged ( self ): return bool(self.diff) @property def changedFields ( self ): return self.diff.keys() def getFieldDiff ( self, field_name ): return self.diff.get(field_name, None) def save ( self, *args, **kwargs ): super(CoolModel, self).save(*args, **kwargs) self.__initial = self._dict @property def _dict ( self ): return model_to_dict(self, fields = [field.name for field in self._meta.fields]) 


The class was written for specific purposes, so the toDict method should be doped with a file, the main thing is that you, I hope, understand how it works. If in short, the whole field-value of the pair is first added to the dictionary (the value depends on the class, at this stage you need to teach the method of how to serialize a particular type / class). Then the entire property-value of a pair from the list of additional properties is added to the dictionary. The property list is not really a list, but a hash table of the form:
 { address: { country: null, state: { type: null, fullTitle: null }, district: null, city: { type: null, fullTitle: null }, streetType: null, fullAddress: null }, type: null } 

This hash table says that from this model you need to select a few more additional properties: address and type. Each of which, in turn, in addition to the field values, must contain several additional properties: contry, state, district, city, streetType, fullAddress for the address property. null means selecting only the fields defined in the model class. Due to such a tree structure of properties properties, it is possible to select nested properties and selections.

Creating a universal handler
In this section, I will tell you how I implemented a unified handler. urls.py add urlpattern to urls.py:
 url(r'^(?P<view>[^/]*)/.*$', 'db.views.page') 

Now add the page method to the views.py file.
Page method
 def page ( req, view ): models = { "countries": Country, "statetypes": StateType, "states": State, "districts": District, "cities": City, "people": Person #... } modelClass = models[view] if view in models else None if view in models: method = req.method properties = json.loads(req.GET.get("properties", "{}")) if method == "GET": id = req.GET.get("id", None) if id: return read(req, modelClass, filters = {"id": id}, properties = properties) else: query = req.GET.get("query", None) start = int(req.GET.get("start", 0)) limit = int(req.GET.get("limit", -1)) if limit < 0: limit = None f = json.loads(req.GET.get("filter", "[]")) s = json.loads(req.GET.get("sort", "[]")) q = None filters = {} for filter in f: filters[filter["property"]] = filter["value"] queryProperties = { "countries": "title__icontains", "states": "title__icontains", "districts": "title__icontains", "cities": "title__icontains", "people": ["lastName__icontains", "firstName__icontains", "middleName__icontains"] #... } if view in queryProperties and query: if isinstance(queryProperties[view], list): for p in queryProperties[view]: q |= Q(**{p: query}) if q else Q(**{p: query}) else: q |= Q(**{queryProperties[view]: query}) if q else Q(**{queryProperties[view]: query}) sorters = ["%s%s" % ("" if k == "ASC" else "-", v) for k, v in s] return read(req, modelClass, start, limit, filters, q, sorters, properties) elif method == "POST": items = json.loads(req.raw_post_data) return create(req, modelClass, items, properties) elif method == "PUT": items = json.loads(req.raw_post_data) return update(req, modelClass, items, properties) elif method == "DELETE": items = json.loads(req.raw_post_data) return delete(req, modelClass, items) elif view in globals(): if not view in ["signin"]: if not req.user.is_authenticated: return JsonResponse({ "success": False, "message": u"  !" }) else: if not req.user.is_superuser: return JsonResponse({ "success": False, "message": u"   !" }) return globals()[view](req) else: return JsonResponse({ "success": False, "message": u"  (%s)  !" % view }) 


The models dictionary contains the key-value of the pair, where the key is the name of the API method, the value is the class of the corresponding model. The variable queryProperties contains the key-value of the pair, where the key is the name of the API method, the value is the field name or a list of such names (with modifications like "__in", "__gt", "__icontains", etc.). The selection will be filtered by the query parameter by the specified fields (filters will be combined with the OR operator).
It remains only to implement the methods create, read, update and delete.
Here is the code for these methods:
@ transaction.commit_manually
def create (req, modelClass, items, properties = None):
results = []
try:
for item in items:
model = modelClass ()
for k, v in item.iteritems ():
if hasattr (model, k):
setattr (model, k, v)
model.save ()
results.append (toJSON (model, properties))
transaction.commit ()
return JsonResponse ({
"Success": True,
"Items": results
})
except Exception, e:
transaction.rollback ()
return JsonResponse ({
"Success": False,
"Message": e.message
})

def read (req, modelClass, start = 0, limit = None, filters = None, q = None, sorters = None, properties = None):
try:
query = modelClass.objects.all ()
if filters:
query = query.filter (** filters)
if q:
query = query.filter (q)
if sorters:
query = query.order_by (sorters)
count = query.count ()
results = toJSON (query [start: (start + limit) if limit else None], properties)
return JsonResponse ({
"Success": True,
"Items": results,
"Total": count
})
except Exception, e:
return JsonResponse ({
"Success": False,
"Message": e.message
})

@ transaction.commit_manually
def update (req, modelClass, items, properties = None):
results = []
try:
for item in items:
try:
model = modelClass.objects.get (id = item ["id"])
for k, v in item.iteritems ():
if hasattr (model, k):
setattr (model, k, v)
model.save ()
results.append (toJSON (model, properties))
except modelClass.DoesNotExist:
pass
transaction.commit ()
return JsonResponse ({
"Success": True,
"Items": results
})
except Exception, e:
transaction.rollback ()
return JsonResponse ({
"Success": False,
"Message": e.message
})

@ transaction.commit_manually
def delete (req, modelClass, items):
try:
for item in items:
modelClass.objects.get (id = item ["id"]). delete ()
transaction.commit ()
return JsonResponse ({
"Success": True
})
except Exception, e:
transaction.rollback ()
return JsonResponse ({
"Success": False,
"Message": e.message
})

Each of the methods performs the creation, selection, modification, and deletion of models, respectively, and returns in response a selection or created / modified models.

PyCharm setup
Actually there is no complicated configuration in PyCharm. To begin, checkout the repository to the local computer:
svn checkout db.tci.lan/svn/db ~/Projects/db
Open in PyCharm and create a Django project from a template. Specify the folder ~/Projects/db as the path (here you need to be careful not to create the folder db in the folder ~/Projects/db ). The path to settings.py should be ~/Projects/db/settings.py . Add automatically created files in SVN, create CRUD, as described above.
You must also create a mod.wsgi file that Apache warned us about.
Its contents may be something like this:
 import os, sys sys.path.append('/home/lufton/public_html/db.tci.lan/public') sys.path.append('C:/Documents and Settings/lufton.TCI/Projects') os.environ['DJANGO_SETTINGS_MODULE'] = 'db.settings' import django.core.handlers.wsgi application = django.core.handlers.wsgi.WSGIHandler() 


Here, sys.path.append used twice in order for the configuration to work on Windows, which I sometimes use.
Now, each commit will not only provide you with version control, but will automatically automatically upload the project to the specified folder, after which the changes will immediately take effect.
Naturally, for local debugging, you will need to configure Django to use a remote database; for this I simply specified the server IP address as the database server.

Configuring Sencha Architect 2
Sencha Architect has a lot of limitations, one of which is due to the fact that it is quite difficult to set up publishing to a selected folder so that loading the project in the browser does not cause complications.
In our case, the publication folder is installed in ~/Projects/db . In the Application properties, set the appFolder: 'js/app' Now, after each posting, you just need to move the file ~/Projects/db/app.js to ~/Projects/db/js/app.js you don’t even have to move the app.js file app.js , because we created our own alias for it and it can be excellently issued upon request to /js/app.js .

Conclusion

Now you have a fully working and customized system for ExtJS and Django development. The development process now is to work with SA. After the next Publish in SA, if necessary, add the newly created model / storage / view / controller files in SVN , if you made changes with Application, you also need to take care of moving the app.js file as described above . Zakommitte project and observe the changes.

Source: https://habr.com/ru/post/167165/


All Articles