Atsushi Odagiri avatar Atsushi Odagiri committed e195f9c Draft

refs #3

Comments (0)

Files changed (3)

addressbook/fields.py

+import formalchemy.fields as fa_fields
+
+class TagListRenderer(fa_fields.TextFieldRenderer):
+    def _deserialize(self, data):
+        if not data:
+            return []
+        return [s.strip() for s in data.split(",")]
+
+    def stringify_value(self, v, as_html=False):
+        return ",".join(v)
+

addressbook/models.py

     UnicodeText,
     Date,
     ForeignKey,
+    Table,
     )
 
 from sqlalchemy.orm import (
     )
 
 from sqlalchemy.ext.declarative import declarative_base
+from sqlalchemy.ext.associationproxy import association_proxy
 from zope.sqlalchemy import ZopeTransactionExtension
 from .modelurl import link_config
 
     DBSession.configure(bind=engine)
     Base.metadata.create_all(bind=engine)
 
+
+person_tag = Table("person_tag", Base.metadata,
+                   Column("person_id", Integer, ForeignKey("persons.id")),
+                   Column("tag_id", Integer, ForeignKey("tags.id")))
+
 @link_config(route_name="person", 
              mapping=[('person_id', 'id')],
              title_attr='first_name')
     category = relationship('Category',
                             backref='persons')
 
+    _tags = relationship("Tag",
+                         secondary=person_tag,
+                         backref="persons")
+
+    tags = association_proxy("_tags", "name",
+                             creator=lambda name: Tag.get_or_create(name))
+
 class Category(Base):
     __tablename__ = 'categories'
     query = DBSession.query_property()
     
     def __unicode__(self):
         return self.name
+
+
+class Tag(Base):
+    __tablename__ = 'tags'
+    query = DBSession.query_property()
+    id = Column(Integer, primary_key=True)
+    name = Column(Unicode(255), nullable=False,
+                  unique=True)
+
+    def __unicode__(self):
+        return self.name
+
+    @classmethod
+    def get_or_create(cls, name):
+        tag = cls.query.filter(cls.name==name).first()
+
+        if tag:
+            return tag
+
+        return cls(name=name)

addressbook/views.py

 from pyramid.view import view_config
 from pyramid.httpexceptions import HTTPFound
-from formalchemy import FieldSet, Grid
+from formalchemy import FieldSet, Grid, Field
 from .models import DBSession, Person, Category
+from .fields import TagListRenderer
 
 @view_config(route_name="top", renderer="index.mako")
 def index(request):
 @view_config(route_name="new_person", renderer="form.mako")
 def new_person(request):
     fs = FieldSet(Person, session=DBSession, data=request.POST)
+    fs.add(Field('tags', renderer=TagListRenderer))
+    fs.configure(exclude=[fs._tags])
+
     if request.POST and fs.validate():
         fs.sync()
+        fs.model.tags = fs.tags.value
         return HTTPFound(request.route_url('top'))
     return dict(fs=fs)
 
     person_id = request.matchdict['person_id']
     person = Person.query.filter(Person.id==person_id).one()
     fs = FieldSet(person, data=request.POST)
+    fs.add(Field('tags', renderer=TagListRenderer,
+                 value=person.tags))
+    fs.configure(exclude=[fs._tags])
     if request.POST and fs.validate():
         fs.sync()
+        fs.model.tags = fs.tags.value
         return HTTPFound(request.route_url('top'))
     return dict(fs=fs)
 
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.