# -*- coding: utf-8 -*- from olo_oahpa.olo_drill.models import * from olo_oahpa.olo_drill.forms import * from olo_oahpa.conf.tools import switch_language_code from django.db.models import Q, Count from django.http import HttpResponse, Http404 from django.shortcuts import get_list_or_404, render_to_response from django.core.exceptions import ObjectDoesNotExist from django.utils.encoding import smart_str, smart_unicode from random import randint import os import re import itertools import olo_oahpa.settings # DEBUG = open('/dev/ttys001', 'w') from random import choice from .forms import PRONOUNS_LIST try: L1 = olo_oahpa.settings.L1 except: L1 = 'olo' # was: sme try: LOOKUP_TOOL = olo_oahpa.settings.LOOKUP_TOOL except: LOOKUP_TOOL = 'lookup' try: FST_DIRECTORY = olo_oahpa.settings.FST_DIRECTORY except: FST_DIRECTORY = False try: DEFAULT_DIALECT = olo_oahpa.settings.DEFAULT_DIALECT except: DEFAULT_DIALECT = None # FST_DIRECTORY = '/opt/smi/sme/bin' #Just testing. Hardcoded here because it looks like looking it up in settings.py failed # LOOKUP_TOOL = '/usr/local/bin/lookup' # Probably delete this: seems to work OK here... def parse_tag(tag): """ Iterate through a tag string by chunks, and check for tag sets and tag names. Return the reassembled tag on success. """ def fill_out(tags): from itertools import product def make_list(item): if type(item) == list: return item else: return [item] return list(product(*map(make_list, tags))) tag_string = [] for item in tag.split('+'): if Tagname.objects.filter(tagname=item).count() > 0: tag_string.append(item) elif Tagset.objects.filter(tagset=item).count() > 0: tagnames = Tagname.objects.filter(tagset__tagset=item) tag_string.append([t.tagname for t in tagnames]) if len(tag_string) > 0: return ['+'.join(item) for item in fill_out(tag_string)] else: return False class Info: pass class Game(object): def __init__(self, settings): self.query_set = False self.lemmas_selected = [] self.form_list = [] self.count = "" self.score = "" self.comment = "" self.settings = settings self.all_correct = "" self.show_correct = 0 self.num_fields = 6 self.global_targets = {} # .has_key deprecated, is there a way to use in with this? if not self.settings.has_key('gametype'): self.settings['gametype'] = "bare" if self.settings['gametype'] == "bare" and self.settings.has_key('pron_type') and self.settings['pron_type'] in ['Rel', 'Dem']: self.num_fields = 4 if self.settings.has_key('semtype'): if self.settings['semtype'] in ('all','All'): # upper- or lowercase # self.settings['semtype'] = self.settings['allsem'] self.settings['semtype'] = 'all' else: semtype = self.settings['semtype'][:] self.settings['semtype'] = [] self.settings['semtype'].append(semtype) def new_game(self): self.form_list = [] word_ids = [] i = 1 num = 0 # if self.settings['pos'] == 'Pron': # print 'omg' # for i in range(self.num_fields): # db_info = {} # db_info['userans'] = "" # db_info['correct'] = "" # errormsg = self.get_db_info(db_info) # form, word_id = self.create_form(db_info, i, 0) # print form # else: # Use this to make sure that pronouns don't have repeated # pronouns existing_tags = [] # Can this be changed? Self.create_form should go without fail. tries = 0 maxtries = 40 while i < self.num_fields and len(self.form_list) < 5 and tries < maxtries: tries += 1 db_info = {} db_info['userans'] = "" db_info['correct'] = "" errormsg = self.get_db_info(db_info) if errormsg and errormsg == "error": # i = i+1 continue # raise Http404(errormsg) form = None try: form, word_id = self.create_form(db_info, i, 0) except Http404, e: raise e except ObjectDoesNotExist: continue # Do not generate same question twice if word_id: num = num + 1 if word_id in set(word_ids): #and not (self.settings['gametype'] == "bare" and self.settings['pron_type'] in ['Rel','Dem']): # If there are less than 5 different lemmas to choose from then this causes a "No questions were able to be generated." continue else: word_ids.append(word_id) self.form_list.append(form) i = i+1 # print len(self.form_list) # if tries == maxtries: # raise Http404('No questions were able to be generated.') # PI: just for debugging if not self.form_list: # No questions found, so the quiz_id must have been bad. raise Http404('Invalid quiz id.') def search_info(self, reObj, string, value, words, t_type): matchObj = reObj.search(string) if matchObj: syntax = matchObj.expand(r'\g') if not words.has_key(syntax): words[syntax] = {} words[syntax][t_type] = value return words def check_game(self, data=None): db_info = {} question_tagObj = re.compile(r'^question_tag_(?P[\w\-]*)$', re.U) question_wordObj = re.compile(r'^question_word_(?P[\w\-]*)$', re.U) question_fullformObj = re.compile(r'^question_fullform_(?P[\w\-]*)$', re.U) answer_tagObj = re.compile(r'^answer_tag_(?P[\w\-]*)$', re.U) answer_wordObj = re.compile(r'^answer_word_(?P[\w\-]*)$', re.U) answer_fullformObj = re.compile(r'^answer_fullform_(?P[\w\-]*)$', re.U) answer_taskwordObj = re.compile(r'^answer_taskword_(?P[\w\-]*)$', re.U) # added by Heli targetObj = re.compile(r'^target_(?P[\w\-]*)$', re.U) # Collect all the game targets as global variables self.global_targets = {} # If POST data was data check, regenerate the form using ids. # This iterates through forms in list of forms for n in range (1, self.num_fields): db_info = {} qwords = {} awords = {} tmpawords = {} # This compiles a dictionary from all of the form fields # {u'answer': u'', # u'userans': u'empty', # u'correct': u'empty', # u'tag_id': u'66', # u'word_id': u'628'} for fieldname, value in data.items(): # print >> DEBUG, d, value if fieldname.count(str(n) + '-') > 0: fieldname = fieldname.lstrip(str(n) + '-') qwords = self.search_info(question_tagObj, fieldname, value, qwords, 'tag') qwords = self.search_info(question_wordObj, fieldname, value, qwords, 'word') qwords = self.search_info(question_fullformObj, fieldname, value, qwords, 'fullform') tmpawords = self.search_info(answer_tagObj, fieldname, value, tmpawords, 'tag') tmpawords = self.search_info(answer_wordObj, fieldname, value, tmpawords, 'word') tmpawords = self.search_info(answer_fullformObj, fieldname, value, tmpawords, 'fullform') tmpawords = self.search_info(answer_taskwordObj, fieldname, value, tmpawords, 'taskword') # added by Heli self.global_targets = self.search_info(targetObj, fieldname, value, self.global_targets, 'target') db_info[fieldname] = value # This appears to not be used for leksa and morfa # Or if it is to be used with morfa, last stanza has problem. # Furthermore, qwords has no keys, and thus doesn't iterate. for syntax in qwords.keys(): if qwords[syntax].has_key('fullform'): qwords[syntax]['fullform'] = [qwords[syntax]['fullform']] # This also appears to not be used for leksa and morfa # Or else there's a problem in the initial forloop. # Dictionary here comes out empty. # tmpawords doesn't iterate here; no keys for syntax in tmpawords.keys(): awords[syntax] = [] info = {} if tmpawords[syntax].has_key('word'): info['word'] = tmpawords[syntax]['word'] if tmpawords[syntax].has_key('tag'): info['tag'] = tmpawords[syntax]['tag'] if tmpawords[syntax].has_key('fullform'): info['fullform'] = [ tmpawords[syntax]['fullform']] if tmpawords[syntax].has_key('taskword'): info['taskword'] = tmpawords[syntax]['taskword'] # added by Heli awords[syntax].append(info) db_info['awords'] = awords db_info['qwords'] = qwords db_info['global_targets'] = self.global_targets #print "db_info['awords'] in check_game " #print db_info['awords'] new_db_info = {} # Generate possible answers for contextual Morfa. if self.settings.has_key('gametype') and self.settings['gametype'] == 'context': new_db_info = self.get_db_info(db_info) if not new_db_info: new_db_info = db_info form, word_id = self.create_form(new_db_info, n, data) if form: self.form_list.append(form) #print form def get_score(self, data): # Add correct forms for words to the page if "show_correct" in data: self.show_correct = 1 for form in self.form_list: form.set_correct() self.count = 2 # Count correct answers: self.all_correct = 0 self.score = "" self.comment = "" i = 0 points = sum([1 for form in self.form_list if form.error == "correct"]) if points == len(self.form_list): self.all_correct = 1 if self.show_correct or self.all_correct: self.score = self.score.join([repr(i), "/", repr(len(self.form_list))]) if (self.show_correct or self.all_correct) and not self.settings['gametype'] == 'qa' : if i == 2: i = 3 if i == 1: i = 2 if self.settings.has_key('language'): language = switch_language_code(self.settings['language']) com_count = Comment.objects.filter(Q(level=i) & Q(lang=language)).count() if com_count > 0: self.comment = Comment.objects.filter(Q(level=i) & Q(lang=language))[randint(0,com_count-1)].comment self.score = '%d/%d' % (points, len(self.form_list)) class BareGame(Game): casetable = { 'N-NOM-PL': ('Nom', ['Pl']), 'N-GEN': ('Gen', ['Sg','Pl']), 'N-PAR': ('Par', ['Sg','Pl']), 'N-ILL': ('Ill', ['Sg','Pl']), 'N-INE': ('Ine', ['Sg','Pl']), 'N-ELA': ('Ela', ['Sg','Pl']), 'N-ALL': ('All', ['Sg','Pl']), 'N-ADE': ('Ade', ['Sg','Pl']), 'N-ABL': ('Abl', ['Sg','Pl']), 'N-TRA': ('Tra', ['Sg','Pl']), 'N-TER': ('Ter', ['Sg','Pl']), 'N-ABESS': ('Abe', ['Sg','Pl']), 'N-COM': ('Com', ['Sg','Pl']), '': '', } # PI: commented out, as it seems to be WIP # def get_db_info_new(self, db_info): # from .forms import GAME_TYPE_DEFINITIONS # from .forms import GAME_FILTER_DEFINITIONS # if 'pos' in self.settings: # pos = self.settings['pos'] # # Where to find the game type for each POS # pos_gametype_keys = { # 'N': ('case', 'number'), # 'V': 'vtype', # 'Der': 'derivation_type', # 'A': 'adjcase', # 'Num': 'num_bare', # 'Pron': 'proncase', # } # game_type_location = pos_gametype_keys.get(pos, False) # gametype = self.settings.get(game_type_location, False) # if not game_type_location: # raise Http404("Undefined POS-form relationship, or wrong type.") # if not gametype: # raise Http404("Game type was not set on the form object.") # game_types = GAME_TYPE_DEFINITIONS.get(pos, False) # game_filters = GAME_FILTER_DEFINITIONS.get(pos, False) # if not game_types: # raise Http404("Undefined POS in game_type_definitions") # possible_types = game_types.get(gametype, False) # if not possible_types: # raise Http404("Undefined type %s" % gametype) # question, answer = choice(possible_types) # answer_tags = parse_tag(answer) # TAG_QUERY = Q(string__in=answer_tags) # tags = Tag.objects.filter(TAG_QUERY).order_by('?') # if len(tags) == 0: # t_q = str(TAG_QUERY) # raise Http404("Oops, no tags matching query!\n\n\t\n%s" % t_q) # # Pronoun type and grade must be filtered here because it affects the # # set of tags available for the next step. # # TODO: grade # # PI: temporary removal # # if 'pron_type' in game_filters: # # ptype = True and self.settings.get('pron_type') or False # # # print ptype # # if ptype: # # tags = tags.filter(subclass=ptype) # # # print tags # # select a random tag and set of forms associated with it to begin # tag = tags[0] # random_form = tag.form_set.order_by('?') # no_forms = True # failure_count = 0 # while no_forms and failure_count < 10: # for filter_ in game_filters: # if filter_ == 'source': # source = True and self.settings.get('book') or False # if source: # random_form = random_form.filter(word__source__name__in=[source]) # # if filter_ == 'stem': # # bisyl = ['2syll', 'bisyllabic'] # # trisyl = ['3syll', 'trisyllabic'] # # Csyl = ['Csyll', 'contracted'] # added for sme # # syll = True and self.settings.get('syll') or [''] # # sylls = [] # # for item in syll: # # if item in bisyl: # # sylls.append('2syll') # # if item in trisyl: # # sylls.append('3syll') # # if item in Csyl: # # sylls.append('Csyll') # # random_form = random_form.filter(word__stem__in=sylls) # # If there are forms left, we select one # if random_form.count() > 0: # no_forms = False # break # else: # # Otherwise try a new tag and form set and run through the # # filters again # tag = tags.order_by('?')[0] # random_form = tag.form_set.order_by('?') # failure_count += 1 # continue # random_form = random_form[0] # db_info['word_id'] = random_form.word.id # db_info['tag_id'] = tag.id def get_db_info(self, db_info): if self.settings.has_key('pos'): pos = self.settings['pos'] # PI: isn't this what the second argument of .get() does?.. syll = True and self.settings.get('syll') or [''] case = True and self.settings.get('case') or "" number = self.settings.get('number', '') noun_type = self.settings.get('noun_type', '') # was: noun_class singular_only = self.settings.get('singular_only', False) # make it possible to only generate singular exercises if the user wishes so # levels = True and self.settings.get('level') or [] adjcase = True and self.settings.get('adjcase') or "" pron_type = True and self.settings.get('pron_type') or "" proncase = True and self.settings.get('proncase') or "" derivation_type = True and self.settings.get('derivation_type') or "" # grade = True and self.settings.get('grade') or "" num_type = True and self.settings.get('num_type') or "" # added to get num_type from settings source = self.settings['book'] mood, tense, infinite, attributive = "", "", "", "" num_bare = "" if 'num_bare' in self.settings: num_bare = self.settings['num_bare'] if 'num_level' in self.settings: num_level = self.settings['num_level'] if 'num_type' in self.settings: # added by Heli num_type = self.settings['num_type'] if 'grade' in self.settings: grade = self.settings['grade'] pos_tables = { "N": case, "A": adjcase, "Num": num_bare, "V": "", "Pron": proncase, "Der": derivation_type, } # sylls = [] # bisyl = ['2syll', 'bisyllabic'] # trisyl = ['3syll', 'trisyllabic'] # Csyl = ['Csyll', 'contracted'] # added for sme # for item in syll: # if item in bisyl: # sylls.append('2syll') # if item in trisyl: # sylls.append('3syll') # if item in Csyl: # sylls.append('Csyll') # if pos == 'Pron': # syll = [''] if pos in ['N', 'Num', 'Pron']: case, number = self.casetable[pos_tables[pos]] else: case = self.casetable[pos_tables[pos]] grade = self.casetable.get('grade', '') num_type = self.casetable.get('num_type', '') # added by Heli, changed by Pavel to skip an exception, change this back I suppose pos_mood_tense = { "PRS": ("Ind", "Prs", ""), "PRT": ("Ind", "Prt", ""), "PRF": ("", "", "PrfPrc"), "GER": ("", "", "Ger"), "COND": ("Cond", "Prs", ""), "IMPRT": ("Imprt", "", ""), "POT": ("Pot", "Prs", "") } if pos == "V" and self.settings.has_key('vtype'): mood, tense, infinite = pos_mood_tense[self.settings['vtype']] pos2 = '' subclass = '' if pos == "Num": if num_type == "A+Ord": # Ordinal numerals have tag A+Ord pos = 'A' #self.settings['pos'] = 'A' pos2 = 'Num' subclass='Ord' elif num_type == "N+Coll": # Collective numerals have tag N+Coll pos = 'N' #self.settings['pos'] = 'N' pos2 = 'Num' subclass='Coll' # PI changed # number = ["Sg","Pl",""] # if case == "Ess": # number = [""] # elif case == "Nom" and pos != "Pron": # number = ["Pl"] # else: # number = ["Sg","Pl"] # A+Sg+Nom # following values are in grade # A+Comp+Sg+Nom # A+Superl+Sg+Nom # following value is in case # A+Attr # if pos == 'A': # if "Attr" in [attributive, case]: # attributive = "Attr" # case = "" # number = [""] maxnum, i = 20, 0 TAG_QUERY = Q(pos=pos) # Exclude derivations by default TAG_EXCLUDES = None # Q(subclass__contains='Der') #PI removed FORM_FILTER = False # Query filtering on words # SUB_QUERY = Q(word__stem__in=sylls) SUB_QUERY = False # NOTE: copied this from questions_install, to make it easier to define # what kind of exercise it is. It would be nice to extend this to everything # because then we can just define a question/answer tag as something like: # question_types = [ # ('V+Inf', 'V+Ind+Prs+Person-Number'), # ('V+Inf', 'V+Pot+Tense+Person-Number'), # ('A+Sg+Nom', 'A+Der/AV+V+Ind+Prs+Person-Number'), # ] # # And it will save the need for a lot of lines of code. def parse_tag(tag): """ Iterate through a tag string by chunks, and check for tag sets and tag names. Return the reassembled tag on success. """ def fill_out(tags): from itertools import product def make_list(item): if type(item) == list: return item else: return [item] return list(product(*map(make_list, tags))) tag_string = [] normalized_tag = [tagname.lower().capitalize() for tagname in tag.split('+')] for item in normalized_tag: if Tagname.objects.filter(tagname=item).count() > 0: tag_string.append(item) elif Tagset.objects.filter(tagset=item).count() > 0: tagnames = Tagname.objects.filter(tagset__tagset=item) tag_string.append([t.tagname for t in tagnames]) if len(tag_string) > 0: return ['+'.join(item) for item in fill_out(tag_string)] else: return False if pos == "Der": derivation_types = { # 'Der/AV': parse_tag("A+Der/AV+V+Mood+Tense+Person-Number"), 'A-DER-V': parse_tag("A+Der/AV+V+Ind+Prs+Person-Number-ConNeg"), 'V-DER-PASS': parse_tag("V+Der/PassL+V+Ind+Tense+Person-Number-ConNeg"), } TAG_QUERY = Q(string__in=derivation_types[derivation_type]) TAG_EXCLUDES = False sylls = False source = False if pos in ['Pron', 'N', 'Num']: TAG_QUERY = TAG_QUERY & \ Q(case=case) # regardless of whether it's Actor, Coll, etc. if pos == 'N': if singular_only: # if the user has checked the box "singular only" TAG_QUERY = TAG_QUERY & Q(number='Sg') else: TAG_QUERY = TAG_QUERY & Q(number__in=number) # 'Pers' subclass for pronouns, otherwise none. # TODO: combine all subclasses so forms can be fetched if pos == 'Pron': sylls = False TAG_QUERY = TAG_QUERY & Q(subclass=pron_type) & Q(number__in=['', 'Pl', 'Sg']) # Sg added by Heli, Du removed by Pavel elif pos2 == 'Num': sylls = False TAG_QUERY = TAG_QUERY & Q(subclass=subclass) # PI: 'subclass' removed from models.py, but that could've been a mistake # else: # TAG_QUERY = TAG_QUERY & Q(subclass='') if pos == 'Num' or pos2 == 'Num': if num_level == '1': # Numerals in Sg on level 1 TAG_QUERY = TAG_QUERY & Q(number='Sg') else: # Numerals in both Sg and Pl on level 1-2 TAG_QUERY = TAG_QUERY & Q(number__in=['Sg','Pl']) if pos == 'V': TAG_QUERY = TAG_QUERY & \ Q(tense=tense) & \ Q(mood=mood) & \ Q(infinite=infinite) if tense != 'Prs': TAG_EXCLUDES = Q(string__contains='ConNeg') if pos == 'A': if pos2 == 'Num': sylls = False TAG_QUERY = TAG_QUERY & Q(subclass=subclass) & Q(case=case) & Q(attributive='') & Q(grade='') else: TAG_QUERY = TAG_QUERY & \ Q(subclass='') & \ Q(attributive=attributive) & \ Q(grade=grade) & \ Q(case=case) & \ Q(number__in=number) # filter can include several queries, exclude must have only one # to work successfully if pos != 'Der': tags = Tag.objects.exclude(string__contains='Der')\ .filter(TAG_QUERY)\ #.exclude(polarity='Neg') # PI: aha, might need subclass after all #.exclude(subclass='Prop')\ else: tags = Tag.objects.filter(TAG_QUERY)# \ # .exclude(subclass='Prop')\ # .exclude(polarity='Neg') if TAG_EXCLUDES: tags = tags.exclude(TAG_EXCLUDES) if tags.count() == 0: keys = ['pos', 'case', 'tense', 'mood', 'attributive', 'grade', 'number'] values = [pos, case, tense, mood, attributive, grade, number] error = "Morfa.get_db_info: Database is improperly loaded.\ No tags for the query were found.\n\n\ %s\n\n\ %s" % (TAG_QUERY, SUB_QUERY) if TAG_EXCLUDES: error += "\nexcludes: %s" % TAG_EXCLUDES raise Http404(error) """if self.settings['pos'] == "Num": if self.settings.has_key('num_level') and str(self.settings['num_level']) == "1": """ # QUERY = Q(pos__iexact=pos) & Q(presentationform__in=smallnum) # else: # QUERY = Q(pos__iexact=pos) if pos == 'Num' or pos2 == 'Num': QUERY = Q(pos__iexact=pos) # & Q(form__tag__subclass=subclass) # PI else: # levels is not what we're looking for QUERY = Q(pos__iexact=pos) & Q(stem__in=syll) #if source and source not in ['all', 'All']: # QUERY = QUERY & Q(source__name=source) # smallnum = ["okta", "guokte", "golbma", "njeallje", "vihtta", "guhtta", # "čieža", "gávcci","ovcci","logi"] # smallnum_ord = ["vuosttaš", "nubbi", "goalmmát", "njealját", "viđát", # "guđát", "čihččet", "gávccát", "ovccát", "logát"] # smallnum_coll = ["guovttis", "guovttes", "golmmas", "njealjis", # "viđás", "guđás", "čiežas", "gávccis","ovccis","logis"] # if pos == 'Num': # and subclass == '': # PI # QUERY = QUERY & Q(lemma__in=smallnum) # if pos2 == 'Num': # and subclass == 'Ord': # PI # QUERY = QUERY & Q(lemma__in=smallnum_ord) error = "Morfa.get_db_info: Database is improperly loaded.\ There are no Words, Tags or Forms, or the query\ is not returning any." NoWordsFound = Http404(error) # settings dialect? UI_Dialect = self.settings.get('dialect', DEFAULT_DIALECT) # if 'dialect' in self.settings: # UI_Dialect = self.settings['dialect'] # else: # UI_Dialect = DEFAULT_DIALECT try: WORD_FILTER = Q() tag = tags.order_by('?')[0] # Process the selection from the noun_type menu (incorporates gender, animacy and inflection type): """if noun_type == "N-NEUT": WORD_FILTER = WORD_FILTER & Q(word__gender='nt') elif noun_type == "N-MASC-INANIM": WORD_FILTER = WORD_FILTER & Q(word__gender='m',word__animate='nn') elif noun_type == "N-MASC-ANIM": WORD_FILTER = WORD_FILTER & Q(word__gender='m',word__animate='aa') elif noun_type == "N-FEM-8": WORD_FILTER = WORD_FILTER & Q(word__gender='f', word__inflection_class__contains='8') elif noun_type == "N-FEM-other": WORD_FILTER = WORD_FILTER & Q(word__gender='f') & (Q(word__lemma__endswith='а') | Q(word__lemma__endswith='я'))""" SOURCE_FILTER = Q() """if source.lower() != 'all': if source == "l1": SOURCE_FILTER = Q(word__chapter__in=['B1','B2','B3','B4','B5','B6','B7','B8','B9','L1','L2','L3','L4','L5']) elif source == "l2": SOURCE_FILTER = Q(word__chapter__in=['B1','B2','B3','B4','B5','B6','B7','B8','B9','L1','L2','L3','L4','L5','L6','L7','L8','L9','L10','L11','L12']) elif source == "l3": SOURCE_FILTER = Q(word__chapter__in=['B1','B2','B3','B4','B5','B6','B7','B8','B9','L1','L2','L3','L4','L5','L6','L7','L8','L9','L10','L11','L12','L13','L14','L15','L16','L17'])""" """ commented out for testing without noun_class normalized_noun_class = [item.lower().capitalize() for item in noun_class.split('-')] for item in normalized_noun_class: tagname = Tagname.objects.get(tagname=item) tagset = tagname.tagset # Oh Lisp macros, where are ye? if tagset.tagset == 'Animate': WORD_FILTER = WORD_FILTER & Q(word__animate=tagname.tagname.lower()) elif tagset.tagset == 'Declension': WORD_FILTER = WORD_FILTER & Q(word__declension=tagname.tagname.lower()) elif tagset.tagset == 'Gender': WORD_FILTER = WORD_FILTER & Q(word__gender=tagname.tagname.lower()) """ no_form = True count = 0 while no_form and count < 10: # Pronouns are a bit different, so we need to resort the tags # PI: Huh? Seems all the same to me. Anyway... if tag.pos == 'Pron': tag = tags.order_by('?')[0] random_word = tag.form_set.filter(WORD_FILTER, SOURCE_FILTER, word__language=L1) # PI: commented out, b/c at this stage # where the Morfa-S semtype has not # been set up we just end up whacking # the random_word set # if not tag.pos in ['Pron', 'Num'] and \ # tag.string.find('Der') < 0: # random_word = random_word.filter(word__semtype__semtype="MORFAS") # if tag.pos == 'Pron': # random_word = random_word\ # .exclude(word__stem='nubbi') # # if sylls: # # random_word = random_word.filter(word__stem__in=sylls) # if source: # random_word = random_word.filter(word__source__in=source) # if pos2 == 'Num': # if subclass == 'Ord': # random_word = random_word.filter(word__lemma__in=smallnum_ord) # added to constrain the set of ordinal numerals # elif subclass == 'Coll': # random_word = random_word.filter(word__lemma__in=smallnum_coll) # constrains the set of collective numerals if random_word.count() > 0: random_form = random_word.order_by('?')[0] random_word = random_form.word #random_loc2 = random_word.loc2 #print random_word no_form = False break elif random_word.count() == 1: random_form = random_word[0] random_word = random_form.word break else: count += 1 continue db_info['word_id'] = random_word.id db_info['tag_id'] = tag.id #print db_info if tag.string.lower().find('conneg') > -1: db_info['conneg'] = choice(PRONOUNS_LIST.keys()) else: db_info['conneg'] = False except IndexError: wc = Word.objects.count() tc = Tag.objects.count() fc = Form.objects.count() wfc = Word.objects.filter(QUERY).count() tfc = Tag.objects.filter(TAG_QUERY).count() if 0 in [tc, wc, fc, wfc, tfc]: # print error error += "Word count (%d), Tag count (%d), Form count (%d), Words matching query (%d), Tags matching query (%d)." % (wc, tc, fc, wfc, tfc) error += "\n Query: %s" % repr(QUERY) error += "\n Tag Query: %s" % repr(TAG_QUERY) raise Http404(error) return def create_form(self, db_info, n, data=None): print "creating form..." #if not 'word_id' in db_info: # return None, None #if self.settings.has_key('dialect'): # UI_Dialect = self.settings['dialect'] #else: # UI_Dialect = DEFAULT_DIALECT language = self.settings['language'] pos = self.settings['pos'] Q_DIALECT = Dialect.objects.get(dialect="main") word = Word.objects.get(id=db_info['word_id']) print "word id: ", db_info['word_id'] print "word: ", word tag = Tag.objects.get(id=db_info['tag_id']) print tag # A little exception for derivation, we want to be able to accept PassS # and PassL, but show only PassL in the answers. # Get the initial form list of forms matching the tag and word id if pos == 'Pron': # Need to filter by the word lemma for pronouns, otherwise # ambiguities arise form_list = Form.objects.filter(tag=tag, word__lemma=word.lemma) elif pos == 'Der': # Search for PassS and PassL forms, filter out later. NB: previous # step only searches for PassL, so at this point we know some PassL # forms exist for the word. tag_strings = [tag.string] if 'Der/PassL' in tag.string: tag_strings.append(tag.string.replace('PassL', 'PassS')) elif 'Der/PassS' in tag.string: tag_strings.append(tag.string.replace('PassS', 'PassL')) form_list = word.form_set.filter(tag__string__in=tag_strings) else: form_list = word.form_set.filter(tag=tag) if not form_list: raise Form.DoesNotExist # TODO: check this, there may be some forms that need to be filtered # here instead. if pos == 'Der': correct = form_list.filter(tag__string__contains='PassL') correct = form_list[0] # Due to the pronoun ambiguity potential (gii 'who', gii 'which'), # we need to make sure that the word is the right one. if pos == 'Pron': word = correct.word # Get word translations for the tooltip target_key = switch_language_code(self.settings['language'][-3::]) translations = sum([w.word_answers for w in word.translations2(target_key).all()],[]) # Get baseform, matching number; except for in essive where # there is no number, and with Nominative, where the test is # about turning nominative singular into nominative plural, # thus all baseforms should be singular. if tag.case in ['Nom'] or tag.attributive: match_number = False else: match_number = True def baseformFilter(form): # Get baseforms, and filter based on dialects. # NOTE: Need to use getBaseform on Form object, not Word, # because Word.getBaseform doesn't pay attention to number. if self.settings.has_key('dialect'): UI_Dialect = self.settings['dialect'] else: UI_Dialect = DEFAULT_DIALECT # Derived forms need return_all=False otherwise derived infinitive # forms may be returned, and we need them to be underived in # presentation of the question wordform. if pos == 'Der': try: bfs = form.getBaseform(match_num=match_number, return_all=False) except: bfs = [form.word] return bfs bfs = form.getBaseform(match_num=match_number, return_all=True) excluded = bfs.exclude(dialects__dialect='NG') if excluded.count() == 0: excluded = bfs filtered = excluded.filter(dialects__dialect=UI_Dialect) # If no non-NG forms are found, then we have to display those. if filtered.count() == 0 and excluded.count() > 0: return list(excluded) else: return list(filtered) base_forms = map(baseformFilter, form_list) # Flatten the lists, but if this isn't an iterateable object, don't worry try: base_forms = sum(base_forms, []) except TypeError: pass # Just in case multiple are returned, get the first. # TODO: make sure no forms that are needed are being lost here. try: baseform = list(set(base_forms))[0] except IndexError: if len(base_forms) == 0: baseform = form.getBaseform(match_num=match_number) # All possible form presentations accepted_answers = form_list.values_list('fullform', flat=True) # Just the ones we want to present for just one dialect #presentation = form_list.filter(dialects=Q_DIALECT) if pos == 'Der': presentation = presentation.filter(tag__string__contains='PassL') # Unless there aren't any ... #if presentation.count() == 0: presentation = form_list # Exclude those that shouldn't be displayed, but should be accepted presentation_ng = presentation.exclude(dialects__dialect='NG') # Unless this results in no forms somehow, in which case we display # them anyway... if presentation_ng.count() == 0: presentation_ng = presentation presentation_ng = presentation_ng.values_list('fullform',flat=True) # Check if the form is connegative, if not, set to false. # NB: this is part of making sure that since the connegative form is # the same for all pronouns, that one pronoun is displayed throughout # all of the steps of the user entering answers and checking that they # are correct. if not db_info.get('conneg', False): db_info['conneg'] = False morph = (MorfaQuestion( word=word, tag=tag, baseform=baseform, correct=correct, accepted_answers=accepted_answers, answer_presentation=presentation_ng, translations=translations, question="", dialect="main", #Q_DIALECT, language=language, userans_val=db_info['userans'], # TODO: userans not in use? correct_val=db_info['correct'], data=data, prefix=n, conneg=db_info['conneg']) ) return morph, word.id class NumGame(Game): generate_fst = 'transcriptor-numbers2text-desc.xfst' answers_fst = 'transcriptor-text2numbers-desc.xfst' def get_db_info(self, db_info): """ Options supplied by views ord, card - obvious kl1 - easy clock (half hours only) kl2 - medium clock (quarter hours) TODO: kl3 - difficult clock (all numbers??) """ numeral="" num_list = [] random_num = randint(1, int(self.settings['maxnum'])) db_info['numeral_id'] = smart_str(random_num) if self.settings['gametype'] == 'ord': db_info['numeral_id'] += u"." return db_info def generate_forms(self, forms, fstfile): import subprocess from threading import Timer lookup = LOOKUP_TOOL gen_norm_fst = FST_DIRECTORY + "/" + fstfile try: open(gen_norm_fst) except IOError: raise Http404("File %s does not exist." % gen_norm_fst) gen_norm_command = [lookup, "-flags", "mbTT", "-utf8", "-d", gen_norm_fst] try: forms.encode('utf-8') except UnicodeDecodeError: pass num_proc = subprocess.Popen(gen_norm_command, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) def kill_proc(proc=num_proc): try: proc.kill() raise Http404("Process for %s took too long." % ' '.join(gen_norm_command)) except OSError: pass return t = Timer(5, kill_proc) t.start() output, err = num_proc.communicate(forms.encode("utf-8")) return output, err def clean_fst_output(self, output): num_tmp = output.decode('utf-8').splitlines() cleaned = [] for num in num_tmp: line = num.strip() # line = line.replace(' ','') if line: nums = line.split('\t') if len(nums) == 3: nums = (nums[0], '?') else: nums = tuple(nums) cleaned.append(nums) return cleaned def strip_unknown(self, analyses): return [a for a in analyses if a[1] != '?'] def check_answer(self, question, useranswer, formanswer): gametype = self.settings['numgame'] # print gametype if useranswer.strip(): forms = useranswer.encode('utf-8') if gametype == 'string': fstfile = self.generate_fst elif gametype == 'numeral': fstfile = self.answers_fst output, err = self.generate_forms(smart_unicode(forms), fstfile) num_list = self.clean_fst_output(output) num_list = self.strip_unknown(num_list) # print repr([question, useranswer, num_list]) # 'string' refers to the question here, not the answer if gametype == 'string': # user answer must match with numeral generated from # the question if useranswer in [a[0] for a in num_list] and \ question in [a[1] for a in num_list]: return True else: return False elif gametype == 'numeral': # Numbers generated from user answer must match up # with numeral in the question num_list = num_list + formanswer try: _ = int(useranswer) return False except ValueError: pass if question in [a[1] for a in num_list] or \ useranswer in num_list: return True else: return False def create_form(self, db_info, n, data=None): if self.settings['gametype'] in ["ord", "card"]: language = L1 else: language = L1 numstring = "" fstfile = self.generate_fst q, a = 0, 1 # production paths lookup = "%s\n" % db_info['numeral_id'] output, err = self.generate_forms(lookup, fstfile) num_tmp = output.splitlines() num_list = [] for num in num_tmp: line = num.strip() # line = line.replace(' ','') if line: nums = line.split('\t') num_list.append(nums[a].decode('utf-8')) try: numstring = num_list[0] except IndexError: error = "Morfa.NumGame.create_form: Database is improperly loaded, \ or Numra is unable to look up words." raise Http404(error) form = (NumQuestion( numeral=db_info['numeral_id'], num_string=numstring, num_list=num_list, gametype=self.settings['numgame'], userans_val=db_info['userans'], correct_val=db_info['correct'], data=data, prefix=n, game=self) ) return form, numstring from forms import KlokkaQuestion class Klokka(NumGame): QuestionForm = KlokkaQuestion generate_fst = 'transcriptor-clock2text-desc.xfst' answers_fst = 'transcriptor-text2clock-desc.xfst' error_msg = "Morfa.Klokka.create_form: Database is improperly loaded, \ or Numra is unable to look up words." def get_db_info(self, db_info): hour = str(randint(0, 23)) if len(hour) == 1: hour = '0' + hour else: hour = str(hour) if self.settings['gametype'] == "kl1": min_options = ['00', '30'] minutes = choice(min_options) elif self.settings['gametype'] == "kl2": min_options = ['00', '15', '30', '45'] minutes = choice(min_options) elif self.settings['gametype'] == "kl3": mins = str(randint(0, 59)) if len(mins) == 1: mins = '0' + mins minutes = mins random_num = '%s:%s' % (hour, minutes) db_info['numeral_id'] = str(random_num) return db_info def check_answer(self, question, useranswer, formanswer): # TODO: in string->num, need to display the corresponding numeral if # it is one that can be 14 hour time gametype = self.settings['numgame'] if useranswer.strip(): forms = useranswer.encode('utf-8') if gametype == 'string': fstfile = self.generate_fst elif gametype == 'numeral': fstfile = self.answers_fst output, err = self.generate_forms(smart_unicode(forms), fstfile) num_list = self.clean_fst_output(output) num_list = self.strip_unknown(num_list) # print repr([question, useranswer, num_list]) # 'string' refers to the question here, not the answer if gametype == 'string': # user answer must match with numeral generated from # the question if useranswer in [a[0] for a in num_list] and \ question in [a[1] for a in num_list]: return True else: return False elif gametype == 'numeral': # Numbers generated from user answer must match up # with numeral in the question # Bug in numeral game seems to be presenting wrong set of numerals, # so if answerset contains 13+, need to remove and take the lower. # Or 'militaryrelax' the answer num_list = num_list + formanswer try: _ = int(useranswer) return False except ValueError: pass if question in [a[1] for a in num_list] or \ useranswer in num_list: return True else: return False def create_form(self, db_info, n, data=None): if self.settings['gametype'] in ["kl1", "kl2", "kl3"]: language = L1 numstring = "" fstfile = self.generate_fst q, a = 0, 1 lookup = "%s\n" % db_info['numeral_id'] # lookup = "%s\n" % db_info['numeral_id'] output, err = self.generate_forms(lookup, fstfile) # norm, allnum = output.split('\n\n')[0:2] norm_list = [] for num in output.decode('utf-8').splitlines(): line = num.strip() if line: nums = line.split('\t') norm_list.append(nums[a]) try: numstring = norm_list[0] except IndexError: raise Http404(self.error_msg) form = (self.QuestionForm( numeral=db_info['numeral_id'], num_string=numstring, present_list=norm_list, accept_list=norm_list, gametype=self.settings['numgame'], userans_val=db_info['userans'], correct_val=db_info['correct'], data=data, prefix=n, game=self) ) return form, numstring ## # # Dato # ## class Dato(Klokka): from forms import DatoQuestion as QuestionForm # QuestionForm = DatoQuestion generate_fst = 'transcriptor-date2text-desc.xfst' answers_fst = 'transcriptor-text2date-desc.xfst' error_msg = "Dato.create_form: Database is improperly loaded, \ or Dato is unable to look up forms." def get_db_info(self, db_info): """ Going to need to subclass this because klokka generates the wrong thing. Lookup format is DD.M. Dato has no difficulty options. """ from random import choice def dayrange(x): return range(1,x+1) # List of tuples with all possible days # built from (month, maxdays) months = [(x, dayrange(y)) for x, y in [(1, 31), (2, 29), (3, 31), (4, 30), (5, 31), (6, 30), (7, 31), (8, 31), (9, 30), (10, 31), (11, 30), (12, 31)]] month, days = choice(months) date = '%d.%d.' % (choice(days), month) db_info['numeral_id'] = str(date) class QuizzGame(Game): def __init__(self, *args, **kwargs): super(QuizzGame, self).__init__(*args, **kwargs) self.init_tags() def init_tags(self): self.settings['gametype'] = "leksa" def get_db_info(self, db_info): # levels = self.settings['level'] semtypes = self.settings['semtype'] geography = self.settings['geography'] frequency = True and self.settings['frequency'] or False # frequency value or False source = self.settings['source'] source_language = self.settings['transtype'][0:3] target_language = self.settings['transtype'][-3::] QueryModel = Word # Excludes excl = ['exclude_' + self.settings['transtype']] error = "QuizzGame.get_db_info: Database may be improperly loaded. \ Query for %s-%s, semantic type %s and book %s returned zero results." % ((source_language, target_language, semtypes, source)) # This query is fairly expensive, and must be run once per game-form generation. Thus, # on the first generation it is run, and the results are stored to a list. # Each successive time this is run after the first query, a word is selected from the list # and popped off. if not self.query_set: leksa_kwargs = {'lang': source_language, 'tx_lang': target_language} excl.append('mPERSNAME') if semtypes and semtypes not in ['all', 'All']: leksa_kwargs['semtype_incl'] = semtypes if source and source not in ['all', 'All']: leksa_kwargs['source'] = source leksa_kwargs['semtype_incl'] = False if geography: leksa_kwargs['geography'] = geography if excl: leksa_kwargs['semtype_excl'] = excl # The following is written by the example of sylls in MorfaS: this # can probably be simplified-- with sylls in MorfaS there was a # time when there were several possible values (3syll, # trisyllabic), but this should be no longer the case... kw_frequency = [] common = ['common', 'common'] rare = ['rare', 'rare'] if frequency: for item in frequency: if item in common: kw_frequency.extend(common) if item in rare: kw_frequency.extend(rare) leksa_kwargs['frequency'] = list(set(kw_frequency)) word_set = leksa_filter(QueryModel, **leksa_kwargs) self.query_set = word_set try: while True: random_word = choice(self.query_set) if random_word[1] not in self.lemmas_selected: break else: continue self.lemmas_selected.append(random_word[1]) # self.query_set.pop(self.query_set.index(random_word)) except IndexError: if len(self.query_set) == 0: raise Http404(error) db_info['word_id'] = random_word[0] db_info['question_id'] = "" return db_info def create_form(self, db_info, n, data=None): tr_lemmas = [] # This is producing an unnecessary query, but it takes a lot of work to switch this # to just passing a word model instead of the ID. # Ideally should pass the model, so there's no need to query it again. word_id = db_info['word_id'] target_language = self.settings['transtype'][-3::] source_language = self.settings['transtype'][0:3] word = Word.objects.get(Q(id=word_id)) translations = word.wordtranslation_set.filter(language=target_language) tr_lemmas.extend([w.definition for w in translations]) # Get correct answers; pick the first (oho!) # Need to not pick the first one. correct = "" preferred = False possible = False stat_pref = False tcomms = False if type(word) == Word: trans_obj = word.translations2(self.settings['transtype']).all() possible = [t.definition for t in trans_obj.filter(tcomm=False)] trans = [t.definition for t in trans_obj] tcomms = [t.definition for t in trans_obj.filter(tcomm=True)] stat_pref = [t.definition for t in trans_obj.filter(tcomm_pref=True)] if len(tcomms) > 0: preferred = [t.definition for t in trans_obj.filter(tcomm=False)] if not correct: if len(stat_pref) > 0: correct = stat_pref[:] elif type(word) == WordTranslation: trans_obj = word.word trans = [trans_obj.lemma] if not correct: correct = trans question_list = [] userans_val = '' try: userans_val = db_info['answer'].strip() except KeyError: userans_val = db_info['userans'] form = (LeksaQuestion( tcomms, stat_pref, preferred, possible, self.settings['transtype'], word, correct, tr_lemmas, question_list, userans_val, db_info['correct'], data, prefix=n,)) return form, word.id