# -*- encoding: utf-8 -*- import time import json import os import shutil import re import Image import base64 import copy import warnings import glob import errno from distutils.dir_util import copy_tree from exceptions import ValidationError, UniqueNameError from bson import ObjectId, DBRef from datetime import datetime from django.conf import settings from django.forms.fields import ImageField from django.core.exceptions import ValidationError as DjangoValidationError from utils.mongo_connection import get_db from utils.mongo_document import Document from utils.strings import multiple_replace from pollster.models import Pollster def is_image_file(value): return hasattr(value, "name") WIDGET_TYPES = ( ('TextInput', 'Texto'), ('MultipleCheckBox', 'Checklist (multiple opción)'), ('RadioButton', 'Checklist (única respuesta)'), ('DropDownList', 'Despliegue de lista (única respuesta)'), ('ImageCheckBox', 'Checklist con imágenes (multiple opción)'), ('ImageRadioButton', 'Checklist con imágenes (única respuesta)'), ) WITH_OPTIONS = [ "MultipleCheckBox", "DropDownList", "RadioButton", "ImageCheckBox", "ImageRadioButton", ] WITH_IMAGES = ["ImageCheckBox", "ImageRadioButton"] class ComponentStructure(ObjectId): def __init__(self, poll=None, *args, **kwargs): super(ComponentStructure, self).__init__(*args, **kwargs) self.poll = poll class AbstracErrorObject(object): ValidationError = ValidationError errors = [] dict_errors = {} class Poll(Document, AbstracErrorObject): collection_name = 'polls' UniqueNameError = UniqueNameError OPEN = "Abierta" CLOSED = "Cerrada" def __init__(self, data={}, *args, **kwargs): super(Poll, self).__init__(data=data, *args, **kwargs) self.results_path = settings.RESULT_BCK_ROOT self.name = data.get('name', None) self.status = data.get('status', Poll.OPEN) self.pollsters = data.get('pollsters', []) self.creation_date = data.get('creation_date', None) self.modification_date = data.get('modification_date', None) @staticmethod def clean_question(question_name): new_string = question_name.encode('ascii', 'ignore') to_replace = [ (" ", ''), ("#", ''), (".", ''), (":", ''), ("?", ''), ("!", ''), ("@", ''), ("$", ''), ("%", ''), ("^", ''), ("&", ''), ("(", ''), (")", ''), ("+", ''), ("=", ''), ("-", ''), ("/", ''), ("\\", ''), ("-", ''), (",", ''), ('"', ''), ("'", '') ] return multiple_replace(new_string, to_replace) def clone(self): clone_data = self.to_python() if clone_data.get('_id', None): del clone_data['_id'] poll_clone = Poll(data=clone_data) poll_clone.name = "clon-%s" % poll_clone.name poll_clone.status = Poll.OPEN poll_clone_id = poll_clone.save() poll_clone = Poll.get(poll_clone_id) clone_structure_data = self.structure.to_python() if clone_structure_data.get('_id', None): del clone_structure_data['_id'] clone_structure = Structure( data=clone_structure_data, poll=poll_clone) clone_structure.save() path = self.structure.get_image_options_path() clone_path = poll_clone.structure.get_image_options_path() copy_tree(path, clone_path) path = self.structure.get_image_path() clone_path = poll_clone.structure.get_image_path() copy_tree(path, clone_path) return poll_clone def delete(self): structure = self.structure if self.structure else None structure_id = structure.id if structure.id else None if structure_id: get_db().structures.remove(ObjectId(structure_id)) # removing image options files if structure: path = structure.get_image_options_path() if os.path.exists(path): shutil.rmtree(path) if self.id: get_db().polls.remove(ObjectId(self.id)) @staticmethod def pollster_assignment(pollster_id, poll_ids): dbref = DBRef(Pollster.collection_name, ObjectId(pollster_id)) ids = [ObjectId(id) for id in poll_ids] get_db().polls.update( {"_id": {"$in": ids}}, {"$push": {"pollsters": dbref}}, False, multi=True ) def get_pollsters(self): pollsters = [] ids = [] for object in self.pollsters: pollster_id = None if isinstance(object, Pollster) or isinstance(object, DBRef): pollster_id = object.id else: pollster_id = object ids.append(pollster_id) pollsters_data = get_db().pollsters.find({"_id": {"$in": ids}}) for data in pollsters_data: pollster = Pollster(data=data) pollsters.append(pollster) return pollsters @property def structure(self): structure_data = get_db().structures.find_one( {'poll.$id': self.id}) structure_id = structure_data['_id'] if structure_data else None return Structure.get(structure_id) if structure_id else Structure( data={}, poll=self) @staticmethod def status_choices(): return ( (Poll.OPEN, 'Abierta'), (Poll.CLOSED, 'Cerrada'), ) def is_open(self): return self.status == Poll.OPEN def to_python(self, with_dates=False): _dict = {} if with_dates: _dict.update({'creation_date': self.creation_date}) _dict.update({'modification_date': self.modification_date}) if self.id: _dict.update({'_id': self.id}) if self.name: _dict.update({'name': self.name}) if self.status: _dict.update({'status': self.status}) # Ensure pollsters to dbref pollsters = [] for object in self.pollsters: pollster_id = None if isinstance(object, Pollster) or isinstance(object, DBRef): pollster_id = object.id else: pollster_id = object dbref = DBRef(Pollster.collection_name, ObjectId(pollster_id)) pollsters.append(dbref) _dict.update({'pollsters': pollsters}) return _dict def validate(self): self.errors = [] if not self.name: msg = "Necesita ingresar un nombre de encuesta." self.errors.append(msg) else: # Check unique name key, Important !!! existing = get_db().polls.find_one( {'name': re.compile("^%s$" % self.name, re.IGNORECASE)}) if existing and existing.get("_id", None) != self.id: msg = u"Poll name '%s' already in use." % self.name self.errors.append(msg) raise Poll.ValidationError(msg) if len(self.errors): raise Poll.ValidationError(str(self.errors)) def save(self): self.validate() poll_id = None now = datetime.now() if self.id is None: self.creation_date = now self.modification_date = now else: self.modification_date = now poll_id = get_db().polls.save(self.to_python(with_dates=True)) return poll_id @staticmethod def get(id=None): poll = None objects = get_db().polls.find({'_id': ObjectId(id)}) if objects.count(): obj = objects[0] poll = Poll(data=obj) return poll # TODO: Test @staticmethod def all(*args, **kwargs): _all = [] for poll_data in get_db().polls.find(**kwargs): _all.append(Poll(data=poll_data)) return _all @staticmethod def assigned_to_pollster(pollster, *args, **kwargs): _all = [] _find = get_db().polls.find({'pollsters.$id': pollster.id}, **kwargs) for pollster_data in _find: _all.append(Poll(data=pollster_data)) return _all def get_template(self, pollster=None): structure_data = get_db().structures.find_one( {'poll.$id': self.id}, fields={'poll': False}) structure_data = structure_data if structure_data else {} structure = Structure(data=structure_data, poll=self) # some cleans for output template data = structure.to_python(with_errors=False, img_serialize=True) for index, group in data["groups"].iteritems(): for index, field in group['fields'].iteritems(): # Hack for Desktop application. # Desktop app don't know process questions without options. widget_type = field['widget_type'] if widget_type == Field.TextInput: options = field.get('options', {}) if not len(options): field['options'].update({ '%s' % Option.get_offset_id(): { 'text': "", 'order': "0" } }) # Filter dependences. Clean when None values if field.get('dependence', False): dependence_values = field.get( 'dependence', {}).get('values', []) field['dependence']['values'] = filter( lambda x: x is not None, dependence_values) if not any(field['dependence']['values']): del field['dependence'] pollsters = self.get_pollsters() if pollster and pollster not in pollsters: message = "Pollster given not belongs to this poll" warnings.warn(message, Warning, stacklevel=2) data.update({ 'poll_id': str(self.id), 'pollster_id': str(pollster.id) if pollster else None, 'poll_name': self.name, 'pollster_username': pollster.username if pollster else None }) # Removing _id key if data.get('_id', None): del data['_id'] _json = json.dumps( data, sort_keys=True, indent=4, separators=(',', ': '), ensure_ascii=False ) return _json @staticmethod def unassigned_to(pollster_id): _all = [] _find = get_db().polls.find({ "pollsters": { "$not": { '$elemMatch': { "$id": ObjectId(pollster_id)} } } }) for poll_data in _find: _all.append(Poll(data=poll_data)) return _all def add_result(self, poll_result_id): dbref = DBRef("poll_results", ObjectId(poll_result_id)) get_db().polls.update({"_id": self.id}, {"$set": {"result": dbref}}) def get_result(self): poll_result_files = self.get_result_files() datas = map(lambda prf: prf.get_data(), poll_result_files) return PollResult(data=datas) def get_questions(self): questions = [] for group in self.structure.groups: for field in group.fields: questions.append(field.name) return questions def get_csv_header(self, with_order=False): MANY_ANSWERS = [ "MultipleCheckBox", "ImageCheckBox", ] ONLY_ANSWER = [ "DropDownList", "RadioButton", "TextInput", "ImageRadioButton" ] structure = self.structure.to_python() header = [] groups = structure['groups'] sorted_groups = sorted(map(lambda x: int(x), groups.keys())) for group_id in sorted_groups: fields = groups[str(group_id)]['fields'] sorted_fields = sorted(map(lambda x: int(x), fields.keys())) for field_id in sorted_fields: field = fields[str(field_id)] order = "%s_%s " % (group_id, field_id) if field['widget_type'] in ONLY_ANSWER: col_name = field['name'] if with_order: col_name = order + col_name header.append(col_name) elif field['widget_type'] in MANY_ANSWERS: cmp_ = lambda x, y: cmp( int(x[1]['order']), int(y[1]['order'])) sorted_options = sorted(field['options'].items(), cmp_) for option_id, option in sorted_options: col_name = option.get("text", option_id) if with_order: col_name = order + col_name header.append(col_name) return ";".join(header) def get_resutls_path(self): results_path = self._results_path poll_id = self.id if poll_id is None: raise Exception("Please, save poll first!!") return os.path.join(results_path, str(poll_id)) def set_results_path(self, path): self._results_path = path results_path = property(get_resutls_path, set_results_path) def get_result_files(self, as_instance_of=None): result_file = PollResultFile if not as_instance_of else as_instance_of results_path = self.results_path result_files = [] for file_path in glob.glob(results_path + '/*'): result_files.append(result_file(file_path)) return result_files def add_result_files(self, path_and_name): results_path = self.results_path cp = lambda src, dst: shutil.copyfile(src, dst) for path, name in path_and_name: src = path file_name = name dst = os.path.join(results_path, file_name) try: cp(src, dst) except IOError as e: if e.errno == errno.ENOENT: os.mkdir(results_path) cp(src, dst) else: raise e def has_result(self, as_instance_of=None): result_file = PollResultFile if not as_instance_of else as_instance_of results = self.get_result_files(as_instance_of=result_file) return len(results) > 0 def remove_results(self): results = self.get_result_files() for r in results: r.delete() class AbstractObject(AbstracErrorObject): @staticmethod def get_offset_id(): return int(time.time() * 1000) class Option(AbstractObject, ComponentStructure): def __init__(self, data={}, *args, **kwargs): super(Option, self).__init__(*args, **kwargs) self.type = data.get('type', None) self.id = data.get('id', None) self.text = data.get('text', None) self.img = data.get('img', None) self.img_name = data.get('img_name', None) self.order = data.get('order', None) self.none = data.get('none', None) weight = data.get('weight', None) self.weight = int(weight) if weight else weight if not self.img_name and is_image_file(self.img): fileExtension = os.path.splitext(self.img.name)[1] self.img_name = '%s%s' % (self.id, fileExtension) @staticmethod def get_img(poll_id, option_id): path = "%s/%s" % ( settings.IMAGE_OPTIONS_ROOT, str(poll_id) ) for file in os.listdir(path): file_name = os.path.splitext(os.path.split(file)[1])[0] if file_name == option_id: return "%s/%s" % (path, file), settings.MEDIA_URL return None, None def get_absolute_path(self): return "%s/%s/%s" % ( settings.IMAGE_OPTIONS_ROOT, str(self.poll.id), self.img_name ) def validate(self, img_validator=None): self.dict_errors = {} self.errors = [] if self.img and is_image_file(self.img): validator = ImageField().to_python if img_validator: validator = img_validator try: img = validator(self.img) except DjangoValidationError, e: self.dict_errors.update( {'img': '%s: %s' % (self.img.name, e.messages[0])}) else: width, height = Image.open(img).size if width > 250 or height > 250: msg = u"Se necesita una imagen menor a 250x250." self.dict_errors.update( {'img': '%s: %s' % (self.id, msg)}) if 'img' in self.dict_errors.keys(): self.img_name = None self.img = None else: self.img.seek(0) if self.weight is None or self.weight == '': msg = u"opcion %s: ponderación requerida." % self.id self.dict_errors.update({'weight': msg}) if self.text and ";" in self.text: msg = u"Las opciones no pueden contener ';'" self.dict_errors.update({'semicolon_error': msg}) self.errors = self.dict_errors.values() if len(self.errors): raise Option.ValidationError(str(self.errors)) def to_python(self, with_errors=False, img_serialize=False): data = {'%s' % self.id: {}} if self.type: data[self.id].update({'type': self.type}) if self.text: data[self.id].update({'text': self.text}) if self.order is not None: data[self.id].update({'order': self.order}) if self.img_name: if img_serialize: img_path = self.get_absolute_path() img_file = open(img_path, 'rb') image_string = base64.b64encode(img_file.read()) img_file.close() data[self.id].update({'img': image_string}) else: data[self.id].update({'img_name': self.img_name}) elif self.type == "img_input": data[self.id].update({'img': None}) if self.weight is not None and self.weight != '': data[self.id].update({'weight': self.weight}) if self.none: data[self.id].update({'none': self.none}) return data class Dependence(AbstracErrorObject): def __init__(self, form=4, values=None): super(Dependence, self).__init__() self.form = int(form) self.values = values def __str__(self): values = list(self.values) if not len(values) or values is None: return "" if len(values) == 1 or len(values) == 2: __str = values[0] return __str.strip(' ') if len(values) % 2 == 0: values = values[:len(values) - 1] if self.form == 4: __str = ' '.join(values) return __str.strip(' ') elif self.form == 3 or len(values) == 3: __str = "( %s ) %s" % ( ' '.join(values[0:3]), ' '.join(values[3:len(values)]), ) return __str.strip(' ') elif self.form == 2: values.reverse() __str = "( %s ) %s" % ( ' '.join(values[0:5]), ' '.join(values[5:len(values)]), ) return __str.strip(' ') elif self.form == 1: values.reverse() __str = "( %s ) %s ( %s )" % ( ' '.join(values[0:3]), values[3], ' '.join(values[4:len(values)]), ) return __str.strip(' ') def to_python(self): return { 'form': self.form, 'values': self.values } def validate(self, options=[]): self.errors = [] if not any(self.values): return operators = [] opt_ids = [] for pos, value in enumerate(self.values): if value is not None: if value in ('OR', 'AND', ''): operators.append((pos, value)) else: opt_ids.append((pos, value)) if (len(opt_ids) - 1) != len(operators): self.errors.append("Bad logic operators.") values_dict = dict(operators + opt_ids) cleaned_data = [None for x in range(0, 7)] for i in range(0, 7): cleaned_data[i] = values_dict.get(i, '') self.values = cleaned_data if len(self.errors): raise Dependence.ValidationError(str(self.errors)) class Field(AbstractObject, ComponentStructure): TextInput = 'TextInput' MultipleCheckBox = 'MultipleCheckBox' RadioButton = 'RadioButton' DropDownList = 'DropDownList' ImageCheckBox = 'ImageCheckBox' ImageRadioButton = 'ImageRadioButton' VALIDATION_RULES = { 'MultipleCheckBox': ( lambda f: f.options and len(f.options) > 0, "Respuesta con checks (multiple selección): necesita " "al menos una opción." ), 'RadioButton': ( lambda f: f.options and len(f.options) > 1, "Respuesta con radios (Solo una selección): necesita " "al menos dos opciones." ), 'DropDownList': ( lambda f: f.options and len(f.options) > 0, "Respuesta con lista de opciones: necesita " "al menos una opción." ), 'ImageCheckBox': ( lambda f: f.options and len(f.options) > 0, "Respuesta con checks (multiple selección): necesita " "al menos una imagen de opción." ), 'ImageRadioButton': ( lambda f: f.options and len(f.options) > 1, "Respuesta con radios (Solo una selección): necesita " "al menos dos imagenes de opciones." ), 'TextInput': (lambda f: True, ""), } def __init__(self, data={}, *args, **kwargs): super(Field, self).__init__(*args, **kwargs) self.poll_id = None order = data.get('order', None) self.order = int(order) if order else order self.name = data.get('name', None) self.img = data.get('img', None) self.uploaded_file = data.get("uploaded_file", None) value = data.get('dependence', None) self.dependence = value if isinstance(value, Dependence): self.dependence = value.to_python() elif isinstance(value, dict): form = value.get('form', 4) values = value.get('values', []) self.dependence = Dependence(form=form, values=values).to_python() elif isinstance(value, list): value = value if isinstance(value, list) else [value] self.dependence = Dependence(values=value).to_python() self.options = [] widget_type = data.get('widget_type', None) if widget_type and widget_type not in dict(WIDGET_TYPES).keys(): raise AttributeError( 'valid widget types are %s' % WIDGET_TYPES.keys().join(', ') ) self.widget_type = widget_type def get_field_offset(self, structure): stop = False field_counts = {} for group in structure.groups: field_counts.update({"%s" % group.order: len(group.fields)}) group_order = 0 for group in structure.groups: for field in group.fields: if field.name == self.name: group_order = group.order break if stop: break offset = 0 for index in range(0, group_order): offset += int(field_counts[str(index)]) return offset + self.order def show_dependence(self): form = self.dependence.get('form', 4) values = self.dependence.get('values', []) return str(Dependence(form=form, values=values)) def add_options(self, data): for id, info in data.iteritems(): opt_data = {'id': id} opt_data.update(info) opt = Option(opt_data, poll=self.poll) self.options = self.options if self.options is not None else [] if opt_data.get('text', None) and opt not in self.options: self.options.append(opt) img = opt_data.get('img', None) or opt_data.get('img_name', None) if img and opt not in self.options: self.options.append(opt) def img_validate(self, img_file): try: ImageField().to_python(img_file) except DjangoValidationError, e: self.img = None raise Field.ValidationError(e.messages[0]) def validate(self, options=[], new_data=None): self.errors = [] rule, msg = Field.VALIDATION_RULES.get(self.widget_type) if not rule(self): self.errors.append(msg) # Validate option of current field for opt in self.options: try: opt.validate() except Option.ValidationError: if self.widget_type != Field.TextInput: self.errors += opt.errors elif "semicolon_error" in opt.dict_errors.keys(): self.errors.append( "El texto default no puede contener ';'") # Validate dependences temporary_structure = new_data if new_data else self.poll.structure has_dependence = self.dependence and any( self.dependence.get('values', {})) if len(options) and has_dependence: prev_options = [] field_offset = self.get_field_offset(temporary_structure) for i in range(0, field_offset): prev_options.extend(options.get(str(i), [])) prev_options_ids = [opt.id for opt in prev_options] values = self.dependence.get('values', []) msg = [] if len(values): for value in values: # TODO: Replace ('OR', 'AND', '') for CONSTANT valid_value = value and value not in ('OR', 'AND', '') if valid_value and value not in prev_options_ids: try: msg.append(str(value)) except: msg.append( "'%s'" % value.encode('ascii', 'ignore')) if len(msg): self.errors += [ 'Dependencias: %s no validas.' % str(', '.join(msg))] # TODO: Test if self.dependence: form = self.dependence.get('form', 4) values = self.dependence.get('values', []) dependence = Dependence(form=form, values=values) try: dependence.validate() except Dependence.ValidationError: msg = u'Error en la formación de la dependencia.' self.errors += [msg] self.dependence['values'] = dependence.values if not self.name: msg = "Necesita ingresar una pregunta" self.errors.append(msg) elif ";" in self.name: msg = "Las preguntas no pueden contener ';'" self.errors.append(msg) if self.uploaded_file: try: self.img_validate(self.uploaded_file) except Field.ValidationError: self.errors.append(u"Pregunta con imagen no válida.") if len(self.errors): raise Field.ValidationError(str(self.errors)) def storing_image(self, path, img_file): img_path = path + '/%s' % img_file.name if os.path.exists(img_path): os.remove(img_path) with open(img_path, 'wb+') as dst: for chunk in img_file.chunks(): dst.write(chunk) def store_image(self, path): if self.uploaded_file: self.storing_image(path, self.uploaded_file) def get_img_name(self): return getattr(self.uploaded_file, "name", self.img) def get_img_absolute_path(self, poll_id): path = settings.IMAGES_ROOT + '/%s' % poll_id return path + "/%s" % self.get_img_name() # TODO: Need refactoring for receive poll_id in other way def to_python(self, with_errors=False, img_serialize=False, poll_id=None): data = {} data.update({ 'name': self.name, 'widget_type': self.widget_type, 'options': {} }) if self.dependence: data.update({'dependence': self.dependence}) img_name = self.get_img_name() if img_name: if img_serialize: img_path = self.get_img_absolute_path(poll_id) img_file = open(img_path, 'rb') image_string = base64.b64encode(img_file.read()) img_file.close() data.update({'img': image_string}) else: data.update({'img': img_name}) if with_errors: data.update({'errors': self.errors}) options = self.options if self.options else [] for option in options: data['options'].update(option.to_python( with_errors=with_errors, img_serialize=img_serialize) ) return {'%d' % self.order: data} class Group(AbstractObject, ComponentStructure): def __init__(self, data={}, *args, **kwargs): super(Group, self).__init__(*args, **kwargs) order = data.get('order', None) self.order = int(order) if order else order self.name = data.get('name', None) self.fields = data.get('fields', []) def add_field(self, field, order): order = int(order) field.order = order fields_pre = self.fields[:order] fields_post = self.fields[order:] self.fields = fields_pre + [field] + fields_post return field def validate(self): self.errors = [] if not self.name: msg = "Necesita ingresar un nombre para el grupo." self.errors.append(msg) if not self.fields: msg = "Necesita al menos una pregunta para un grupo." self.errors.append(msg) if len(self.errors): raise Group.ValidationError(str(self.errors)) def to_python(self, with_errors=False, img_serialize=False, poll_id=None): data = {'name': self.name, 'fields': {}} if with_errors: data.update({'errors': self.errors}) for field_obj in self.fields: field_data = field_obj.to_python( with_errors=with_errors, img_serialize=img_serialize, poll_id=poll_id ) data['fields'].update(field_data) return {'%d' % self.order: data} class Structure(AbstractObject, ComponentStructure): """ { 'groups': { '0': { 'name': 'group name', 'fields': { '0': { 'widget_type': ..., 'name': ..., 'options': ..., 'dependence': ..., }, '1' ... } }, '1' ... ... } } """ def __init__(self, data={}, poll=None, *args, **kwargs): super(Structure, self).__init__(poll, *args, **kwargs) self.data = data self.groups = [] self.poll = poll # Getting parent poll id from {poll obj | poll dbref | data['poll_id']} self._poll_id = getattr(poll, 'id', None) if self.data and self._poll_id is None: poll_dbref = data.get('poll', None) poll_id = poll_dbref.id if poll_dbref else None self._poll_id = str(poll_id) if poll_id else self._poll_id self._poll_id = self.data.get('poll_id', self._poll_id) # REQUIRE: parent poll id !!! if not self._poll_id: raise Exception('INTERNAL ERROR: A structure need a poll id!') # TODO: Test for refatoring => inherit Document class # Getting id self.id = None _id = data.get('id', None) or data.get('_id', None) if _id and (isinstance(_id, str) or isinstance(_id, unicode)): self.id = ObjectId(_id) elif _id and isinstance(_id, ObjectId): self.id = _id # Build model Structure obj based in dict data groups_info = data.get('groups', {}) for group_order, group_data in groups_info.iteritems(): group = Group({ 'order': group_order, 'name': group_data['name'] }, poll=self.poll) fields_info = group_data.get('fields', {}) for field_order, field_data in fields_info.iteritems(): field_data.update({'order': field_order}) field = Field(field_data, poll=self.poll) field.add_options(field_data.get('options', {})) group.add_field(field, field_order) self.add_group(group, group_order) @property def poll_id(self): return self._poll_id def add_group(self, group, order): order = int(order) group.order = order groups_pre = self.groups[:order] groups_post = self.groups[order:] self.groups = groups_pre + [group] + groups_post return group def get_options(self): fields = reduce( lambda x, y: x + y, [g.fields for g in self.groups], []) options = reduce( lambda x, y: x + y, [f.options or [] for f in fields], []) return options def get_enumerate_options(self, order=None): options = {} for group in self.groups: for field in group.fields: field_offset = field.get_field_offset(self) options.update({"%s" % field_offset: field.options}) return options def get_image_media_url(self): return settings.IMAGES_MEDIA_URL + '/%s' % self.poll_id def get_image_options(self): options = self.get_options() return filter(lambda opt: opt.img_name is not None, options) def get_images_fields(self): fields = [] for group in self.groups: for field in group.fields: fields.append(field) fields = filter(lambda field: field.get_img_name() is not None, fields) return [field.get_img_name() for field in fields] def validate(self): self.errors = [] if not self.data.get('groups', {}): msg = "Necesita al menos un grupo con preguntas." self.errors.append(msg) if len(self.errors): raise Group.ValidationError(str(self.errors)) def is_valid(self, new_data=None): valid = True options = self.get_enumerate_options() temporary_structure = Structure(data=new_data) if new_data else None for group in self.groups: try: group.validate() except Group.ValidationError: valid = False for field in group.fields: try: field.validate(options, new_data=temporary_structure) except Field.ValidationError: valid = False try: self.validate() except Structure.ValidationError: valid = False return valid def to_python(self, with_errors=False, img_serialize=False): data = {'groups': {}} for group_obj in self.groups: data['groups'].update( group_obj.to_python( with_errors=with_errors, img_serialize=img_serialize, poll_id=self.poll_id ) ) if self.id: data.update({'_id': ObjectId(self.id)}) return data def save(self): structure_id = None self.validate() _dict = self.to_python() # Prepare dbref to poll object if not self.poll: raise ValidationError("Need a parent poll.") else: dbref = DBRef(Poll.collection_name, ObjectId(self.poll.id)) _dict.update({'poll': dbref}) # Prepare id if is a existing Structure object if self.id: _dict.update({'_id': ObjectId(self.id)}) # Save process -> Update if it has an id, else insert structure_id = get_db().structures.save(_dict) # Removing older img options files current_options = self.get_image_options() current_opts_file_name = [opt.img_name for opt in current_options] path = self.get_image_options_path() for file in os.listdir(path): if file not in current_opts_file_name: try: os.remove("%s/%s" % (path, file)) except: pass # Removing older img files current_img_fields = self.get_images_fields() path = self.get_image_path() for file in os.listdir(path): if file not in current_img_fields: try: os.remove("%s/%s" % (path, file)) except: pass return structure_id @staticmethod def get(id=None): structure = None objects = get_db().structures.find({'_id': ObjectId(id)}) if objects.count(): obj = objects[0] poll_id = obj['poll'].id structure = Structure(obj) structure.poll = Poll.get(poll_id) return structure def get_image_options_tmp_path(self): path = settings.IMAGE_OPTIONS_ROOT + '/%s/tmp' % self.poll_id try: os.makedirs(path) except OSError: pass return path def get_image_options_path(self): path = settings.IMAGE_OPTIONS_ROOT + '/%s' % self.poll_id try: os.makedirs(path) except OSError: pass return path def get_image_options_tmp_media_url(self): media_url = None media_url = settings.IMAGE_OPTIONS_MEDIA_URL + '/%s/tmp' % self.poll_id return media_url def image_rollback(self): tmp_path = self.get_image_options_tmp_path() path = self.get_image_options_path() for file in os.listdir(path): if file != "tmp": src_file = os.path.join(path, file) dst_file = os.path.join(tmp_path, file) shutil.move(src_file, dst_file) def __storing_image_options(self, path, options): for img_opt in options: with open(path + '/%s' % img_opt.img_name, 'wb+') as dst: for chunk in img_opt.img.chunks(): dst.write(chunk) def get_image_path(self): path = settings.IMAGES_ROOT + '/%s' % self.poll_id try: os.makedirs(path) except OSError: pass return path def save_image_fields(self): for group in self.groups: for field in group.fields: field.store_image(path=self.get_image_path()) def save_image_options(self, tmp=False): options = self.get_image_options() valid_img_options = filter( lambda opt: not 'img' in opt.dict_errors.keys(), options) imgs_to_store = filter( lambda opt: opt.img is not None, valid_img_options) tmp_path = self.get_image_options_tmp_path() path = self.get_image_options_path() if len(imgs_to_store): if tmp: self.__storing_image_options(tmp_path, imgs_to_store) else: self.__storing_image_options(path, imgs_to_store) if not tmp: # Moving tmp images options to the final place for store them for img_opt in valid_img_options: #for img_opt in imgs_to_store: src = '%s/%s' % (tmp_path, img_opt.img_name) dst = '%s/%s' % (path, img_opt.img_name) if os.path.exists(src): shutil.move(src, dst) try: os.removedirs(tmp_path) except: # TODO: tmp must be empty, # if raise exception here is someting bad # TODO: LOG! pass class NodePollResult(object): def __init__(self, id_, answers): self.id = id_ self.answers = answers def sorted(self): answers = self.answers['answers'] sorted_groups = sorted(map(lambda x: int(x), answers.keys())) for group_id in sorted_groups: fields = answers[str(group_id)]['fields'] sorted_fields = sorted(map(lambda x: int(x), fields.keys())) for field_id in sorted_fields: yield fields[str(field_id)] class PollResult(object): def __init__(self, data=None): super(PollResult, self).__init__() _merge = [] _data = {} if isinstance(data, list): _data = copy.deepcopy(data[0]) if len(data) else {} if _data.get('result', None): del _data['result'] # Merge results new_id = 0 for partial_res in data: for id, value in partial_res['result'].iteritems(): if len(value.get('answers', {})): _merge.append((str(new_id), value)) new_id += 1 _data['result'] = dict(_merge) else: _data = data self._data = copy.deepcopy(_data) del _data self._poll_id = self._data.get('poll_id', None) self.id = self._data.get('_id', None) def get_poll(self): return Poll.get(self._poll_id) def to_python(self): return self._data def get_csv_header(self, with_order=False): poll = self.get_poll() try: poll_type = self._data['poll_type'] except: raise Exception("Can't export to csv format") else: poll_record = [] if poll_type == 'general': poll_record = [ 'RUEE', 'DEPARTAMENTO', 'NUM_ESC', 'GRADO', 'GRUPO', 'TIPO_GRUPO' ] elif poll_type == 'monitoreo': poll_record = [ 'RUEE', 'DEPARTAMENTO', 'NUM_ESC', 'GRADO', 'GRUPO', 'TIPO_GRUPO', 'CI', 'NOMBRE' ] poll_header = poll.get_csv_header(with_order=with_order) header = poll_record + poll_header.split(";") return ";".join(header) def to_csv(self): poll = self.get_poll() poll_csv_header = self.get_csv_header(with_order=True) header_order = poll_csv_header.split(";") MANY_ANSWERS = [ "MultipleCheckBox", "ImageCheckBox", ] ONLY_ANSWER = [ "DropDownList", "RadioButton", "TextInput", "ImageRadioButton" ] results = [] for index_record, respuesta in self._data["result"].iteritems(): poll_col_names = poll.get_csv_header(with_order=True).split(";") aux = dict([(x, "") for x in poll_col_names]) for key, value in respuesta['polled'].iteritems(): if key in header_order: aux[key] = value for index_group, data_group in respuesta['answers'].iteritems(): for index_preg, data_preg in data_group['fields'].iteritems(): order = u"%s_%s " % (index_group, index_preg) widget_type = data_preg['widget_type'] name = data_preg['name'] name = unicode(name, "utf-8") if isinstance( name, str) else name name = order + name answer = data_preg['answer'] if len(answer) and widget_type in ONLY_ANSWER: if widget_type == "TextInput": aux[name] = answer.values()[0].get("text", "-") else: aux[name] = str( answer.values()[0].get("weight", "-")) elif len(answer) and widget_type in MANY_ANSWERS: for opt_id, opt_value in answer.iteritems(): text = opt_value.get('text', None) col_name = text if text else opt_id col_name = order + col_name aux[col_name] = str(opt_value.get("weight", "-")) record = [] for key in header_order: record.append(aux[key]) record = ";".join(record) + ";" results.append(record) poll_csv_header = self.get_csv_header() + ";\n" return poll_csv_header + "\n".join(results) def sorted(self): data = self._data['result'] sorted_keys = sorted(map(lambda x: int(x), data.keys())) for result_id in sorted_keys: yield NodePollResult(str(result_id), answers=data[str(result_id)]) class PollResultFile(object): def __init__(self, file_path): self.file_path = file_path self.name = os.path.basename(self.file_path) with open(self.file_path) as f: f.seek(0) self.json_str = f.read() self.data = json.loads(self.json_str, 'utf-8') def get_ruees(self): results = self.data['result'] all_ruees = [] for i in results: ruee = results[i]['polled']['RUEE'] all_ruees.append(ruee) return set(all_ruees) def get_pollster_username(self): return self.data['pollster_username'] def get_polled_count(self): polled_count = len(self.data['result'].keys()) return polled_count def get_file_name(self): return self.name def get_upload_timestamp(self): time_string = self.data.get('upload_timestamp', None) return time_string def set_upload_timestamp(self, time_string): data = self.data data['upload_timestamp'] = time_string self._save() def _save(self): data = self.data with open(self.file_path, 'w') as file_: j = json.dumps(data, sort_keys=True, indent=4, separators=(',', ': ')) file_.seek(0) file_.write(j) def save(self): name = self.get_file_name() file_path = self.file_path poll_id = self.data['poll_id'] poll = Poll.get(poll_id) poll.add_result_files([(file_path, name)]) def get_data(self): return self.data def get_absolute_url(self): poll_id = str(self.data['poll_id']) return os.path.join( settings.RESULT_BCK_URL, poll_id, self.get_file_name()) def delete(self): file_path = self.file_path os.remove(file_path)