diff --git a/helios/datatypes/__init__.py b/helios/datatypes/__init__.py index 961848374ed44824861d6c54a79053c07a93776b..d336b00cde8632a7d7ef4279cdbe8cc908edc32e 100644 --- a/helios/datatypes/__init__.py +++ b/helios/datatypes/__init__.py @@ -1,5 +1,29 @@ """ The Helios datatypes are RDF datatypes that map to JSON-LD + +A datatype object wraps another object and performs serialization / de-serialization +to and from that object. For example, a Helios election is treated as follows: + + helios_election = get_current_election() # returns a helios.models.Election object + + # dispatch to the right contructor via factory pattern + # LDObject knows about base classes like Election, Voter, CastVote, Trustee + # and it looks for the datatype field within the wrapped object to determine + # which LDObject subclass to dispatch to. + ld_object = LDObject.instantiate(helios_election) + + # get some JSON-LD + ld_object.serialize() + +And when data comes in: + + # the type is the base type, Election, CastVote, Trustee, ... + # if this is raw JSON, then this invokes the legacy LDObject parser + # if this is JSON-LD, then it finds the right LDObject based on the declared type + # in the JSON-LD. + # the optional type variable is necessary for legacy objects (otherwise, what is the type?) + # but is not necessary for full JSON-LD objects. + LDObject.deserialize(json_string, type=...) """ from helios import utils @@ -19,6 +43,19 @@ class LDObject(object): # fields to serialize FIELDS = [] + @classmethod + def instantiate(cls, obj): + if not hasattr(obj, 'datatype'): + raise Exception("no datatype found") + + # parse datatype string "v31/Election" --> from v31 import Election + parsed_datatype = obj.datatype.split("/") + + # construct it + dynamic_cls = getattr(__import__(".".join(parsed_datatype[:-1]), globals(), locals(), [], level=-1), parsed_datatype[len(parsed_datatype)-1]) + + return dynamic_cls(obj) + def set_from_args(self, **kwargs): for f in self.FIELDS: if kwargs.has_key(f): diff --git a/helios/migrations/0002_v3_1_new_election_and_voter_fields.py b/helios/migrations/0002_v3_1_new_election_and_voter_fields.py index e75643d805af504ee5b6ec6275150b2c876b8062..56bd20b9d90cdb3d76ed4693ad02cc91a6e72b84 100644 --- a/helios/migrations/0002_v3_1_new_election_and_voter_fields.py +++ b/helios/migrations/0002_v3_1_new_election_and_voter_fields.py @@ -24,8 +24,7 @@ class Migration(SchemaMigration): db.add_column('helios_voter', 'voter_email', self.gf('django.db.models.fields.CharField')(max_length=250, null=True), keep_default=False) # Adding field 'Election.datatype' - # manually tweaked default value to ensure proper datatype for older elections - db.add_column('helios_election', 'datatype', self.gf('django.db.models.fields.CharField')(default='legacy/election', max_length=250), keep_default=False) + db.add_column('helios_election', 'datatype', self.gf('django.db.models.fields.CharField')(default='2011/01/Election', max_length=250), keep_default=False) # Adding field 'Election.election_type' db.add_column('helios_election', 'election_type', self.gf('django.db.models.fields.CharField')(default='election', max_length=250), keep_default=False) diff --git a/helios/migrations/0003_v3_1_election_specific_voters_with_passwords.py b/helios/migrations/0003_v3_1_election_specific_voters_with_passwords.py index 8053a8e882d71390424ffb1c7f2c6625a80d14fd..762f72b549ee4dd6c8335bd85a1c1c323820a073 100644 --- a/helios/migrations/0003_v3_1_election_specific_voters_with_passwords.py +++ b/helios/migrations/0003_v3_1_election_specific_voters_with_passwords.py @@ -72,7 +72,7 @@ class Migration(DataMigration): 'archived_at': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True'}), 'cast_url': ('django.db.models.fields.CharField', [], {'max_length': '500'}), 'created_at': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}), - 'datatype': ('django.db.models.fields.CharField', [], {'default': "'2011/01/election'", 'max_length': '250'}), + 'datatype': ('django.db.models.fields.CharField', [], {'default': "'2011/01/Election'", 'max_length': '250'}), 'description': ('django.db.models.fields.TextField', [], {}), 'election_type': ('django.db.models.fields.CharField', [], {'default': "'election'", 'max_length': '250'}), 'eligibility': ('auth.jsonfield.JSONField', [], {'null': 'True'}), diff --git a/helios/models.py b/helios/models.py index 6e0e3f92bd9d77f344c0c9bdd511c50a4083c125..a8abc3a9aa384792428af4b46cb779bdcc7777cd 100644 --- a/helios/models.py +++ b/helios/models.py @@ -37,7 +37,7 @@ class Election(models.Model, electionalgs.Election): # code, both for crypto and serialization # v3 and prior have a datatype of "legacy/election" # v3.1 and above have a specific datatype of "2011/01/election" - datatype = models.CharField(max_length=250, null=False, default="2011/01/election") + datatype = models.CharField(max_length=250, null=False, default="2011/01/Election") short_name = models.CharField(max_length=100) name = models.CharField(max_length=250) diff --git a/helios/tests.py b/helios/tests.py index 10fdb91562e1ef258c510a9283e55634de96716f..a6e52525e7717534c168daf53501e2c169aa18e0 100644 --- a/helios/tests.py +++ b/helios/tests.py @@ -5,6 +5,8 @@ Unit Tests for Helios import unittest, datetime, re import models +import datatypes + from auth import models as auth_models from views import ELGAMAL_PARAMS import views @@ -213,6 +215,17 @@ class CastVoteModelTests(TestCase): def test_cast_vote(self): assert False + +class DatatypeTests(TestCase): + fixtures = ['election.json'] + + def setUp(self): + self.election = models.Election.objects.all()[0] + + def test_instantiate(self): + ld_obj = datatypes.LDObject.instantiate(self.election) + + ## ## Black box tests ##