# -*- encoding: utf-8 -*- import time import json import os import shutil import re import Image import base64 import copy import warnings from distutils.dir_util import copy_tree from exceptions import * 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 django.core.files.uploadedfile import InMemoryUploadedFile from utils.mongo_connection import get_db from utils.mongo_document import Document from utils.strings import multiple_replace from pollster.models import Pollster WIDGET_TYPES = ( ('TextInput', 'Respuesta en texto'), ('MultipleCheckBox', 'Respuesta con checks (multiple selección)'), ('RadioButton', 'Respuesta con radios (Solo una selección)'), ('DropDownList', 'Respuesta con lista de opciones'), ('ImageCheckBox', 'Respuesta con imagenes tipo checks'), ('ImageRadioButton', 'Respuesta con imagenes tipo radios'), ) 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.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) 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': "", } }) # 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): object_data = get_db().poll_results.find_one( {'poll_id': str(self.id)}) if object_data: return PollResult(object_data) return None 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): MANY_ANSWERS = [ "MultipleCheckBox", "ImageCheckBox", ] ONLY_ANSWER = [ "DropDownList", "RadioButton", "TextInput", "ImageRadioButton" ] structure = self.structure header = [] for group in structure.groups: for field in group.fields: if field.widget_type in ONLY_ANSWER: header.append(field.name) elif field.widget_type in MANY_ANSWERS: for option in field.options: header.append(option.id) return ";".join(header) 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) weight = data.get('weight', None) self.weight = int(weight) if weight else weight if not self.img_name and isinstance(self.img, InMemoryUploadedFile): 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 def get_absolute_path(self): return "%s/%s/%s" % ( settings.IMAGE_OPTIONS_ROOT, str(self.poll.id), self.img_name ) def validate(self): self.dict_errors = {} self.errors = [] if self.type == "img_input" and not self.img and not self.img_name: msg = u"opcion %s: imagen requerida." % self.id self.dict_errors.update({'type': msg}) if self.img and isinstance(self.img, InMemoryUploadedFile): try: img = ImageField().to_python(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}) 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.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}) if self.weight is not None and self.weight != '': data[self.id].update({'weight': self.weight}) 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) order = data.get('order', None) self.order = int(order) if order else order self.name = data.get('name', 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 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: # TODO: Refactoring this. # HORRIBLE path to avoid validation for TextInput options. if self.widget_type != Field.TextInput: try: opt.validate() except Option.ValidationError: self.errors += opt.errors # 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 # TODO: Test if not self.name: msg = "Necesita ingresar una pregunta" self.errors.append(msg) if len(self.errors): raise Field.ValidationError(str(self.errors)) def to_python(self, with_errors=False, img_serialize=False): data = {} data.update({ 'name': self.name, 'widget_type': self.widget_type, 'options': {} }) if self.dependence: data.update({'dependence': self.dependence}) 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): 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 ) 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_options(self): options = self.get_options() return filter(lambda opt: opt.img_name is not None, options) 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 ) ) 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 have 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 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: if isinstance(img_opt.img, InMemoryUploadedFile): with open(path + '/%s' % img_opt.img_name, 'wb+') as dst: for chunk in img_opt.img.chunks(): dst.write(chunk) dst.close() def save_image_options(self, tmp=False): options = self.get_options() valid_img_options = filter( lambda opt: not 'img' in opt.dict_errors.keys(), options) tmp_path = self.get_image_options_tmp_path() if len(valid_img_options): if tmp: self.__storing_image_options(tmp_path, valid_img_options) else: path = self.get_image_options_path() self.__storing_image_options(path, valid_img_options) # Moving tmp images options to the final place for store them for img_opt in valid_img_options: src = '%s/%s' % (tmp_path, img_opt.img_name) dst = '%s/%s' % (path, img_opt.img_name) try: shutil.move(src, dst) except Exception: # TODO: LOG orphan img options pass try: os.removedirs(tmp_path) except: # TODO: tmp must be empty, # if raise exception here is someting bad # TODO: LOG! pass class PollResult(AbstractObject): def __init__(self, data=None): super(PollResult, self).__init__() _merge = [] _data = {} if isinstance(data, list): _data = copy.deepcopy(data[0]) 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) @staticmethod def get(id=None): poll_result = None objects = get_db().poll_results.find({'_id': ObjectId(id)}) if objects.count(): obj = objects[0] poll_result = PollResult(obj) return poll_result def get_poll(self): return Poll.get(self._poll_id) def validate(self): self.errors = [] poll = self.get_poll() if poll.is_open(): self.errors.append('No se pueden subir resultados \ de una encuesta con estado ABIERTA.') if len(self.errors): raise PollResult.ValidationError(str(self.errors)) def is_valid(self): try: self.validate() except PollResult.ValidationError: return False return True def to_python(self): return self._data def save(self): poll_result_id = None poll_result_data = get_db().poll_results.find_one( {'poll_id': self._poll_id}) if poll_result_data: inc_poll_result = PollResult(data=[self._data, poll_result_data]) inc_poll_result.validate() poll_result_id = get_db().poll_results.update( {'_id': poll_result_data['_id']}, inc_poll_result.to_python() ) else: poll_result_id = get_db().poll_results.save(self.to_python()) return poll_result_id def get_csv_header(self): 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' ] header = poll_record + poll.get_csv_header().split(";") return ";".join(header) + ";" def to_csv(self): poll_csv_header = self.get_csv_header() 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(): aux = dict([(x, "") for x in header_order]) 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(): widget_type = data_preg['widget_type'] name = data_preg['name'] name = unicode(name, "utf-8") if isinstance( name, str) else 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(): aux[opt_id] = str(opt_value.get("weight", "-")) record = [] for key in header_order: record.append(aux[key]) record = ";".join(record) results.append(record) return poll_csv_header + "\n" + ";\n".join(results)