This document describes how to add a model to openPLM.
Note
You should not use your production environment for development purpose. It’s recommanded to initiate a development environment:
- copy openPLM’s directory in another place
- change your settings: use a sqlite3 database (the file settings_tests.py is a good candidate for a settings file)
- checks that your settings do not conflict with another installation of openPLM (DOCUMENTS_DIR, etc.)
- run ./manage.py sql all
- run ./manage.py syncdb --all (this should ask you if you want to create a superuser, accept it)
- run migrate --all --fake
- edit the superuser profile (model UserProfile in the plmapp section) and set him as an administrator and a contributor
The first step is simple: just run the command ./manage.py startapp app_name in the openPLM directory (replace app_name by the name of your new application). In this how-to, we will call it bicycle. The name of the application must be a valid Python module name.
Then you must registered your application: edit the variable INSTALLED_APPS in the file openPLM/settings.py and add your application ('openPLM.app_name'). Do the same things for openPLM/settings_tests.py
For example:
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.admin',
'django.contrib.comments',
'django.contrib.humanize',
'djcelery',
'haystack',
'south',
'openPLM.plmapp',
# you can add your application after this line
'openPLM.bicycle',
)
Note
The application must be added after 'openPLM.plmapp'.
Now you can edit the file openPLM/app_name/models.py, and add some useful imports
1 2 3 4 5 6 | from django.db import models
from django.contrib import admin
from openPLM.plmapp.models import Part
from openPLM.plmapp.controllers import PartController
from openPLM.plmapp.utils import get_next_revision
|
First, we import models and admin. We need models to define our models, and we need admin to register our model and make it available on the admin interface.
Next, we import some classes and functions from openPLM.plmapp:
- Part is the base class for models which describe a part;
- PartController is the base class for part’s controller;
- get_next_revision() is an utility function.
After the import, you can add a new class. The name of this class, would be the name displayed in the type field in search/creation pages.
In our case, we call it Bicycle:
class Bicycle(Part):
Now, we can add fields to our models. A field describe which data would be saved in the database.
1 2 3 4 5 6 7 | class Bicycle(Part):
# bicycle fields
nb_wheels = models.PositiveIntegerField("Number of wheels", default=lambda: 2)
color = models.CharField(max_length=25, blank=False)
details = models.TextField(blank=False)
|
We add 3 fields:
- nb_wheels
as it says, the number of wheels
This field is a PositiveIntegerField. As first argument, we give a more comprehensive name that would be displayed in the attributes page.
We also set a default value for this field with default=lambda: 2. The default argument does not take a value but a function which does takes no argument and return a value. Here, we use a lambda expression which always returns 2.
- color
the color of the bicycle
This field is a CharField. With a CharField, you must define the max_length argument. This is the maximal number of characters that can be stored in the database for this field.
We also set blank to True, this means that this field can be empty in a form.
- details
a field for quite long details
This field is a TextField. This is a field that is displayed with a textarea in forms.
See also
The Django model field reference for all types of field that can be used and their arguments
By default, the fields are not displayed. To select which fields should be displayed, you can override the method attributes like in the example above:
1 2 3 4 5 | @property
def attributes(self):
attrs = list(super(Bicycle, self).attributes)
attrs.extend(["nb_wheels", "color", "details"])
return attrs
|
attributes is a read-only property(). This property is a list of attributes, so all elements must be a valid field name. For example, "spam" is not valid since spam is not a field of Bicycle. The property should return attributes from the parent class (Part in our case). That’s why we call super(). In our case, we extends Part attributes with nb_wheels, color and details
There are other methods that can be overridden to select attributes displayed in creation/modification forms.
These methods are listed in PLMObject.
Here, we will excluded details from a creation form:
1 2 3 | @classmethod
def excluded_creation_fields(cls):
return super(Bicycle, cls).excluded_creation_fields() + ["details"]
|
This code is similar to the attributes property. Nevertheless, PLMObject.excluded_creation_fields() is a classmethod() since we do not have a PLMObject when we build a creation form.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class Bicycle(Part):
# bicycle fields
nb_wheels = models.PositiveIntegerField("Number of wheels", default=lambda: 2)
color = models.CharField(max_length=25, blank=False)
details = models.TextField(blank=False)
# bicycle properties
@property
def attributes(self):
attrs = list(super(Bicycle, self).attributes)
attrs.extend(["nb_wheels", "color", "details"])
return attrs
# excluded_creation_fields
@classmethod
def excluded_creation_fields(cls):
return super(Bicycle, cls).excluded_creation_fields() + ["details"]
# end Bicycle
|
To make our model available on the admin interface, we just have to add this line:
1 | admin.site.register(Bicycle)
|
Then you must create a migration for your application. openPLM uses South to manage migrations of database. You can also read the South’s tutorial.
/manage.py schemamigration app_name --initial
This command will create a app_name/migrations directory.
./manage.py migrate app_name
This command will create the table in the database.
./manage.py rebuild_index
This command will rebuild the search index.
Then you can run the server with ./runserver.sh. Open the url http://localhost:8000/home/ and try to create a bicycle.
The model describes only which data should be stored on the database and which attributes should be displayed/editable. You can change how the objects are manipulated by redefining the PLMObjectController of your model.
In this tutorial, we will change the behaviour when a bicycle is revised. Here is the code.
1 2 3 4 5 6 7 8 9 10 11 12 13 | class BicycleController(PartController):
def revise(self, new_revision):
if new_revision == get_next_revision(get_next_revision(self.revision)):
self.details += """
----------------
hello easter egg
----------------
"""
self.save()
return super(BicycleController, self).revise(new_revision)
# end BicycleController
|
As you can see, we create a class called BicycleController which inherits from PartController. A PartController is a controller which manages some specifities of the parts like their children. Since Bicycle inherits from Part, our controller inherits from PartController. If you name a controller like modelController, it will be associated to the model named model.
In our case, we just override the method PartController.revise() by adding a detail if the user forget a revision (for example, c instead of b). Of course, you can write what you want and, for example, not take care of new_revision.
See also
controllers and How to add a controller for more details about controllers.
You can add a tab in the object view by overriding the property PLMObject.menu_items with something like this:
@property
def menu_items(self):
items = list(super(Bicycle, self).menu_items)
items.extend(["mytab1", "mytab2"])
return items
You have to associate this tabs to views. First, you must add a file called urls.py in your application directory (DO NOT modify the file openPLM/urls.py). In this file, you should define a variable urlpatterns.
In this tutorial, we will not add a tab but we will change the page which displays the attributes.
Our urls.py looks like this:
1 2 3 4 5 6 | from django.conf.urls.defaults import *
import openPLM.bicycle.views as views
urlpatterns = patterns('',
(r'^object/Bicycle/([^/]+)/([^/]+)/attributes/$', views.attributes),
)
|
To understand this few lines, you can read the django documentation about urls. Here, we say that an url identifying the page attributes of an object of type Bicycle should be handle by the function attibutes from the module view of the application bicycle.
Now, we can edit the file bicycle/views.py and write the function attributes:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | from openPLM.plmapp.base_views import get_generic_data
from openPLM.plmapp.utils import r2r
def attributes(request, obj_ref, obj_revi):
"""Custom attributes page """
obj_type = "Bicycle"
obj, ctx = get_generic_data(request, obj_type, obj_ref, obj_revi)
object_attributes = []
for attr in obj.attributes:
item = obj.get_verbose_name(attr) + ":" # <- this is our small modification
object_attributes.append((item, getattr(obj, attr)))
ctx.update({'current_page':'attributes',
'object_attributes': object_attributes})
return r2r('attributes.html', ctx, request)
|
This code was taken from plmapp/views.py and slighty modified. Here, you can write what you want, but you may need to read the source of plmapp and inspect the templates.
If you write your own controllers or your own views, you can test them. You can modify the file tests.py in your application directory.
You can check if your models and controllers pass the standart tests by writting something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | from openPLM.plmapp.tests.controllers import PartControllerTest
from openPLM.plmapp.tests.views import PartViewTestCase
from openPLM.bicycle.models import BicycleController
class BicycleControllerTest(PartControllerTest):
TYPE = "Bicycle"
CONTROLLER = BicycleController
DATA = {"nb_wheels" : 2}
class BicycleViewTestCase(PartViewTestCase):
TYPE = "Bicycle"
DATA = {"nb_wheels" : 2,
"color" : "blue"}
# our custom attributes view adds the ":" so, we redefine this test
def test_display_attributes(self):
response = self.client.get(self.base_url + "attributes/")
self.assertEqual(response.status_code, 200)
self.failUnless(response.context["object_attributes"])
attributes = dict(response.context["object_attributes"])
# name : empty value
self.assertEqual(attributes["name:"], "")
# owner and creator : self.user
self.assertEqual(attributes["owner:"], self.user)
self.assertEqual(attributes["creator:"], self.user)
def test_display_attributes2(self):
response = self.client.get(self.base_url + "attributes/")
self.assertEqual(response.status_code, 200)
self.failUnless(response.context["object_attributes"])
attributes = dict(response.context["object_attributes"])
self.assertEqual(attributes["Number of wheels:"], self.DATA["nb_wheels"])
self.assertEqual(attributes["color:"], self.DATA["color"])
self.assertEqual(attributes["details:"], "")
|
Add your application to the settings_tests.py file.
You can run the tests with the command ./manage.py test app_name --settings=settings_tests.
See also
The django documentation about tests: http://docs.djangoproject.com/en/1.2/topics/testing/ .