# -*- coding: utf-8 -*- from oahpa.drills.models import * from oahpa.drills.forms import * 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 random import randint #from django.contrib.admin.views.decorators import _encode_post_data, _decode_post_data import os import re import itertools import settings # DEBUG = open('/dev/ttys001', 'w') def relax(strict): """ Returns a list of relaxed possibilities, making changes by relax_pairs. Many possibilities are generated in the event that users are inconsistent in terms of substituting one letter but not substituting another, however, *all* possibilities are not generated. E.g., *ryøjnesjäjja is accepted for ryöjnesjæjja (user types ø instead of ö consistently) ... but ... *töølledh is not accepted for töölledh (user mixes the two in one word) Similarly, directionality is included. is accepted for <ï>, but not vice versa. E.g.: *ååjmedïdh is not accepted for ååjmedidh, ... but ... *miele is accepted for mïele. """ relaxed = strict sub_str = lambda _string, _target, _sub: _string.replace(_target, _sub) relax_pairs = { # key: value # key is accepted for value u'ø': u'ö', u'ä': u'æ', u'i': u'ï' } # Create an iterator. We want to generate as many possibilities as # possible (very fast), so more relaxed options are available. searches = relax_pairs.items() permutations = itertools.chain(itertools.permutations(searches)) perms_flat = sum([list(a) for a in permutations], []) # Individual possibilities relaxed_perms = [sub_str(relaxed, R, S) for S, R in perms_flat] # Possibilities applied one by one for S, R in perms_flat: relaxed = sub_str(relaxed, R, S) relaxed_perms.append(relaxed) # Return list of unique possibilities relaxed_perms = list(set(relaxed_perms)) return relaxed_perms class Info: pass class Game: def __init__(self, settings): 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.has_key('semtype'): if self.settings['semtype'] == 'all': self.settings['semtype'] = self.settings['allsem'] 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 # TODO: find indexerror, is it here? need to find a way # to pass indexerror into http404 with useful meaning; qid # or whatever 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): 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.') 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) 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') 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']] awords[syntax].append(info) # print info db_info['awords'] = awords db_info['qwords'] = qwords db_info['global_targets'] = self.global_targets 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) 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 = { 'NOMPL' : 'Nom', 'ATTR':'Attr', 'N-ILL':'Ill', 'N-ESS':'Ess', 'N-GEN':'Gen', 'N-INE':'Ine', 'N-ELA':'Ela', 'N-ACC':'Acc', 'N-COM':'Com', '': '' } def get_baseform(self, word_id, tag): basetag = None if tag.pos in ["N", "A", "Num", "Pron"]: if tag.number and tag.case != "Nom": tagstring = tag.pos + "+" + tag.number + "+Nom" else: tagstring = tag.pos + "+Sg" + "+Nom" if Form.objects.filter(word__pk=word_id, tag__string=tagstring).count() > 0: basetag = Tag.objects.filter(string=tagstring)[0] if tag.pos=="V": tagstring = "V+Inf" if Form.objects.filter(word__pk=word_id,tag__string=tagstring).count() > 0: basetag = Tag.objects.filter(string=tagstring)[0] return basetag def get_db_info(self, db_info): if self.settings.has_key('pos'): pos = self.settings['pos'] syll = True and self.settings.get('syll') or "All" case = True and self.settings.get('case') or "" levels = True and self.settings.get('level') or [] adjcase = True and self.settings.get('adjcase') or "" grade = True and self.settings.get('grade') or "" source = "" mood, tense, attributive = "", "", "" num_bare = "" # if self.settings.has_key('syll'): # syll = self.settings['syll'] # if self.settings.has_key('case'): # case = self.settings['case'] # if self.settings.has_key('level'): # levels = self.settings['level'] # if self.settings.has_key('adjcase'): # adjcase = self.settings['adjcase'] if self.settings.has_key('book'): source = self.settings['book'] if self.settings.has_key('num_bare'): num_bare = self.settings['num_bare'] if self.settings.has_key('num_level'): num_level = self.settings['num_level'] # if self.settings.has_key('grade'): # grade = self.settings['grade'] pos_tables = { "N": case, "A": adjcase, "Num": num_bare, "V": "" } sylls = [] bisyl = ['2syll', 'bisyllabic'] trisyl = ['3syll', 'trisyllabic'] for item in syll: if item in bisyl: sylls.extend(bisyl) if item in trisyl: sylls.extend(trisyl) if pos == 'Pron': syll = [''] case = self.casetable[pos_tables[pos]] pos_mood_tense = { "PRS": ("Ind", "Prs"), "PRT": ("Ind", "Prt"), "COND": ("Cond", "Prs"), "IMPRT": ("Imprt", "Prs"), "POT": ("Pot", "Prs") } if pos == "V" and self.settings.has_key('vtype'): mood, tense = pos_mood_tense[self.settings['vtype']] number = ["Sg","Pl",""] if case == "Nom" and pos != "Pron": number = ["Pl"] # A+Sg+Nom # A+Comp+Sg+Nom # A+Superl+Sg+Nom # A+Attr if pos == "A": if case == "Attr": attributive = "Attr" grade = "" case = "" number = "" elif case in ["Comp", "Superl"]: grade = case case = "Nom" ; number = "Sg" attributive = "" maxnum, i = 20, 0 TAG_QUERY = Q(pos=pos) TAG_EXCLUDES = Q(personnumber="ConNeg") & Q(subclass="Prop") FORM_FILTER = False # Query filtering on words # SUB_QUERY = Q(word__stem__in=sylls) SUB_QUERY = False if pos in ['Pron', 'N']: TAG_QUERY = TAG_QUERY & \ Q(possessive="") & \ Q(case=case) if pos == 'Pron': sylls = False if pos == 'V': TAG_QUERY = TAG_QUERY & \ Q(tense=tense) & \ Q(mood=mood) sylls = False if pos == 'A': base_set = Form.objects.filter(tag__string='A+Sg+Nom') TAG_QUERY = Q(attributive=attributive) & \ Q(grade=grade) & \ Q(case=case) & \ Q(number=number) SUB_QUERY = Q(word__form__tag__attributive=attributive) & \ Q(word__form__tag__grade=grade) & \ Q(word__form__tag__case=case) & \ Q(word__form__tag__number=number) # filter can include several queries, exclude must have only one # to work successfully tags = Tag.objects.filter(TAG_QUERY).exclude(personnumber='ConNeg').exclude(subclass='Prop') # TODO: fix this Http404 so it gives back clearer feedback if tags.count() == 0: error = "Morfa.get_db_info: Database is improperly loaded.\ No tags for the query were found.\n\n" error += repr((pos, case, tense, mood, attributive, grade, number)) raise Http404(error) # print self.settings.get('pos'), self.settings.get('num_level'), str(self.settings.get('num_level')) if self.settings['pos'] == "Num": if self.settings.has_key('num_level') and str(self.settings['num_level']) == "1": smallnum = ["1","2","3","4","5","6","7","8","9","10"] QUERY = Q(pos__iexact=pos) & Q(presentationform__in=smallnum) else: QUERY = Q(pos__iexact=pos) 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) 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) try: # Previously the same Tag was being grabbed by the same query, twice # This should be easier. tag = tags.order_by('?')[0] if SUB_QUERY: no_form = True count = 0 while no_form and count < 10: random_word = tag.form_set.filter(word__language=L1)\ .filter(word__stem__in=sylls)\ .order_by('?')[0].word random_form = random_word.form_set.filter(SUB_QUERY)\ .filter(tag=tag) if random_form.count() > 0: random_form = random_form[0] no_form = False break else: count += 1 continue else: no_form = True count = 0 while no_form and count < 10: random_word = tag.form_set.filter(word__language='sma') if sylls: random_word = random_word.filter(word__stem__in=sylls) if random_word.count() > 0: random_form = random_word.order_by('?')[0] random_word = random_form.word no_form = False break else: count += 1 continue db_info['word_id'] = random_word.id db_info['tag_id'] = tag.id 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): language = self.settings['language'] pos = self.settings['pos'] if not db_info.has_key('word_id'): return None, None word_id = db_info['word_id'] tag_id = db_info['tag_id'] tag = Tag.objects.get(id=tag_id) if pos == 'Pron': form_list = Form.objects.filter(tag=tag) else: form_list = Form.objects.filter(word__id=word_id, tag=tag) if not form_list: raise Form.DoesNotExist correct = form_list[0] if pos in ['N', 'V', 'A']: word = Word.objects.get(Q(id=word_id)) # Preserve number in nouns: Sg-Sg, Pl-Pl elif pos == 'Pron': word = correct.word # 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 ['Ess', 'Nom']: match_number = False else: match_number = True # Here we use Form.getBaseForm, because Word.baseform doesn't # pay attention to number. # base_forms = [form.getBaseform(match_num=match_number) for form in form_list] base_forms = [] for form in form_list: try: bf = form.getBaseform(match_num=match_number) except Form.DoesNotExist: raise ObjectDoesNotExist base_forms.append(bf) # Just in case multiple forms are returned; make them unique and # get the first. I feel like this may lead to problems # TODO: allomorphy in baseforms? What to do? baseform = list(set(base_forms))[0] target_key = switch_language_code(self.settings['language'][-3::]) # if target_key == "sma": # translations = sum([w.word_answers for w in word.translations.all()],[]) # else: translations = sum([w.word_answers for w in word.translations2(target_key).all()],[]) fullforms = form_list.values_list('fullform',flat=True) morph = (MorfaQuestion( word=word, tag=tag, baseform=baseform, correct=correct, fullforms=fullforms, translations=translations, question="", dialect="", language=language, userans_val=db_info['userans'], # TODO: userans not in use? correct_val=db_info['correct'], data=data, prefix=n) ) return morph, word_id class NumGame(Game): 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 = [] from random import choice if self.settings['gametype'] in ["ord", "card"]: random_num = randint(1, int(self.settings['maxnum'])) elif self.settings['gametype'] in ["kl1", "kl2", "kl3"]: 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) if self.settings['gametype'] == "ord": db_info['numeral_id'] = str(random_num) + "." else: db_info['numeral_id'] = str(random_num) return db_info def create_form(self, db_info, n, data=None): if self.settings['gametype'] in ["ord", "kl1", "kl2", "kl3"]: language = L1 else: #language=self.settings['numlanguage'] language = L1 numstring = "" # TODO: use settings.py for these. Perhaps abstract lookup calls # to some additional app outside of smadrill # Add generator call here # fstdir="/Users/pyry/gtsvn/gt/" + language + "/bin" # lookup = '/Users/pyry/bin/lookup' #lookup ="/Users/cipriangerstenberger/bin/lookup" if self.settings['gametype'] in ["ord", "card"]: fstfile = "sma-num.fst" q, a = 0, 1 elif self.settings['gametype'] in ["kl1", "kl2", "kl3"]: fstfile = "iclock-sma.fst" # production paths fstdir="/opt/smi/" + language + "/bin" lookup = "/opt/sami/xerox/c-fsm/ix86-linux2.6-gcc3.4/bin/lookup" gen_norm_fst = fstdir + "/" + fstfile # TODO: uncomment dev stuff # lookup = "/Users/pyry/bin/lookup" # gen_norm_fst = "/Users/pyry/gtsvn/gt/sma/bin/" + fstfile # os.popen stuff # gen_norm_lookup = "echo " + db_info['numeral_id'] + " | " + lookup + " -flags mbTT -utf8 -d " + gen_norm_fst # num_tmp = os.popen(gen_norm_lookup).readlines() import subprocess # Need to use subprocess.Popen, because there's a good chance # that a failed command results in victorio eating up memory gen_norm_command = [lookup, "-flags", "mbTT", "-utf8", "-d", gen_norm_fst] num_proc = subprocess.Popen(gen_norm_command, stdin=subprocess.PIPE, stdout=subprocess.PIPE) output, err = num_proc.communicate(input=db_info['numeral_id'] + '\n') 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(db_info['numeral_id'], # numstring, # num_list, # self.settings['numgame'], # db_info['userans'], # db_info['correct'], # data, # prefix=n)) 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) ) return form, numstring class QuizzGame(Game): def get_db_info(self, db_info): # levels = self.settings['level'] semtypes = self.settings['semtype'] geography = self.settings['geography'] source = self.settings['source'] maxnum, i = 20, 0 while i < maxnum: i = i + 1 source_language = self.settings['transtype'][0:3] target_language = self.settings['transtype'][-3::] QueryModel = Word QUERY = Q(semtype__semtype__in=semtypes) & Q(language=source_language) if geography: QUERY = QUERY & Q(geography=geography) # Excludes excl = 'exclude_' + self.settings['transtype'] if source and source not in ['all', 'All']: # s = Source.objects.get(name=source) source = [source] QUERY = QUERY & Q(source__name__in=source) # Annotating with count to filter out Word2nob with no Word # normally would use Wordnob.translations but appears not to # be in use in db. word_set__count won't work either. error = "QuizzGame.get_db_info: Database may be improperly loaded. \ Query for semantic type %s and book %s returned zero results." % ((semtypes, source)) # if QueryModel == Word: try: word_set = QueryModel.objects\ .filter(wordtranslation__language=target_language)\ .annotate(num_xlations=Count('wordtranslation'))\ .filter(num_xlations__gt=0)\ .filter(QUERY)\ .exclude(semtype__semtype=excl) if not geography: word_set = word_set.exclude(semtype__semtype=['PLACES']) random_word = word_set.order_by('?')[0] except IndexError: if QueryModel.objects.filter(QUERY).count() == 0: raise Http404(error) db_info['word_id'] = random_word.id 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)) # Database change means this is no longer needed # synwords = WordTranslation.objects\ # .filter(language=target_language)\ # .filter(wordid=word.lemma) # print synwords # print 'synwords: ', `synwords` # print repr(word) # fail, check word to see what translations or words it has # at this point we shouldn't be getting word_ids that have fails # find synonymous words and pick them to the translations translations = word.wordtranslation_set.filter(language=target_language) tr_lemmas.extend([w.definition for w in translations]) # tr_lemmas.extend([w.definition for w in WordTranslation]) # for s in synwords: # target_key = self.settings['transtype'][-3::] # print s # if type(s) == Word: # translations = s.translations2(target_key).all() # trs = [w.definition for w in translations] # elif type(s) == WordTranslation: # translations = s.word # trs = [translations.lemma] # # trs = [w.lemma for w in translations] # for t in trs: # tr_lemmas.append(t) # 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[:] # if not correct: # correct = preferred[:] # else: # if not correct: # correct = trans elif type(word) == WordTranslation: # Pick one # trans_obj = word.word_set.all()[0] # trans = trans_obj.lemma # Use all trans_obj = word.word trans = [trans_obj.lemma] if not correct: correct = trans question_list = [] # cheat mode for us poor people who don't speak sørsamisk yet # but have to program. ;) # if type(correct) == list: # print repr(word), ', '.join([c.encode('utf-8') for c in correct]) # elif type(correct) in [unicode, str]: # print repr(word), correct.encode('utf-8') 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