# -*- coding: utf-8 -*- from smaoahpa.drills.models import * from smaoahpa.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 # 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 while i < self.num_fields and num < 30: # print i db_info = {} db_info['userans'] = "" db_info['correct'] = "" errormsg = self.get_db_info(db_info) if errormsg and errormsg == "error": i = i+1 continue try: form, word_id = self.create_form(db_info, i, 0) except Http404, e: raise e except ObjectDoesNotExist: i = i + 1 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 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"]: 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": "" } 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']] if case == "Attr": attributive = "Attr" case = "" if grade == "POS": grade = "" if grade == "COMP": grade = "Comp" if grade == "SUPERL": grade = "Superl" number = ["Sg","Pl",""] if case == "Nom": number = ["Pl"] maxnum, i = 20, 0 # vals = {'adjcase': adjcase, 'attributive': attributive, 'case': case, 'grade': grade, 'levels': levels, 'mood': mood, 'num_bare': num_bare, 'num_level': num_level, 'pos': pos, 'syll': syll, 'tense': tense} # print repr(vals) TAG_QUERY = Q(pos=pos) & Q(possessive="") & \ Q(case=case) & Q(tense=tense) & Q(mood=mood) & \ ~Q(personnumber="ConNeg") & Q(attributive=attributive) & \ Q(grade=grade) & Q(number__in=number) tags = Tag.objects.filter(TAG_QUERY) # problem was number__in -> number ? # TODO: fix this Http404 so it gives back clearer feedback if tags.count() < 1: 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: tag = Tag.objects.filter(TAG_QUERY).order_by('?')[0] # Have to do this because some words don't have forms words_with_forms = Word.objects.annotate(num_forms=Count('form'))\ .filter(num_forms__gt=0) random_word = words_with_forms.filter(QUERY).order_by('?')[0] db_info['word_id'] = random_word.id db_info['tag_id'] = tag.id return 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() # TODO: Add a log message about this, or something more detailed for admins to get at... E.g., what query was run. if 0 in [tc, wc, fc, wfc, tfc]: # print error raise Http404(error) if random_word.form_set.count() == 0: raise Http404() def create_form(self, db_info, n, data=None): language = self.settings['language'] 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) basetag = self.get_baseform(word_id, tag) form_list = Form.objects.filter(word__id=word_id, tag=tag) # Q(word__pk=word_id) & Q(tag__pk=tag.id)) if not form_list: raise Form.DoesNotExist # Http404("No forms found for word %s." % str(word_id)) correct = form_list[0] word = Word.objects.get(Q(id=word_id)) if word.pos in ['N', 'V']: # Preserve number in nouns: Sg-Sg, Pl-Pl baseform = word.form_set.filter(tag=basetag)[0] # @cip: this causes a DjangoUnicodeDecodeError in the running application on victorio # print baseform, correct # baseform_list = Form.objects.filter( # Q(word__pk=word.id) & Q(tag=basetag)) # baseform_list = word.form_set.filter(tag__id=tag.id, tag=basetag) # word.translations doesn't exist # translations = word.translations.all().values_list('lemma',flat=True) # Temporary norwegian setting ... # XSLS # translations = word.translations2('nob').all().values_list('lemma', flat=True) # translations = [w.definition for w in word.translations2('nob').all()] 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('nob').all()],[]) fullforms = form_list.values_list('fullform',flat=True) # TODO: which fullform is baseform? # print repr(word), repr(correct) morph = (MorfaQuestion( word=word, tag=tag, baseform=baseform, # word.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): numeral="" num_list = [] random_num = randint(1, int(self.settings['maxnum'])) 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'] == "ord": language="sma" else: #language=self.settings['numlanguage'] language="sma" numstring = "" # TODO: use settings.py for these. Perhaps abstract lookup calls # to some additional app outside of drills # Add generator call here # fstdir="/Users/pyry/gtsvn/gt/" + language + "/bin" # lookup = '/Users/pyry/bin/lookup' #lookup ="/Users/cipriangerstenberger/bin/lookup" fstdir="/opt/smi/" + language + "/bin" lookup = "/opt/sami/xerox/c-fsm/ix86-linux2.6-gcc3.4/bin/lookup" gen_norm_fst = fstdir + "/" + language + "-num.fst" gen_norm_lookup = "echo " + db_info['numeral_id'] + " | " + lookup + " -flags mbTT -utf8 -d " + gen_norm_fst num_tmp = os.popen(gen_norm_lookup).readlines() num_list = [] for num in num_tmp: line = num.strip() line = line.replace(' ','') if line: nums = line.split('\t') num_list.append(nums[1].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'] source = self.settings['source'] maxnum, i = 20, 0 while i < maxnum: i = i + 1 if self.settings['transtype'].startswith("sma"): QueryModel = Word elif self.settings['transtype'] == "nobsma": QueryModel = Wordnob # swesma ; was if -- does it affect logic? elif self.settings['transtype'] == "swesma": QueryModel = Wordswe elif self.settings['transtype'] == "engsma": QueryModel = Wordeng elif self.settings['transtype'] == "deusma": QueryModel = Worddeu elif self.settings['transtype'] == "smesma": QueryModel = Wordsme else: raise Http404("Empty else branch!") # if self.settings['level'].count('all') > 0: # QUERY = Q(semtype__semtype__in=semtypes) # else: # semtypes = self.settings['allsem'] QUERY = Q(semtype__semtype__in=semtypes) # & Q(source__name__in=source) # print repr(QueryModel) 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: random_word = QueryModel.objects.filter(QUERY).order_by('?')[0] # print random_word.lemma except IndexError: if QueryModel.objects.filter(QUERY).count() == 0: raise Http404(error) elif QueryModel == Wordnob: try: random_word = Word.objects.annotate(num_xlations=Count('translations2nob')).\ filter(num_xlations__gt=0).filter(QUERY).order_by('?')[0].translations2('nob').all()[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 # else: # print "else translations count ", translations.count() def create_form(self, db_info, n, data=None): tr_lemmas = [] word_id = db_info['word_id'] if self.settings['transtype'].startswith("sma"): QueryModel = Word elif self.settings['transtype'] == "nobsma": QueryModel = Wordnob # swesma ; was if -- does it affect logic? elif self.settings['transtype'] == "swesma": QueryModel = Wordswe elif self.settings['transtype'] == "engsma": QueryModel = Wordeng elif self.settings['transtype'] == "deusma": QueryModel = Worddeu elif self.settings['transtype'] == "smesma": QueryModel = Wordsme else: raise Http404("Empty else branch!") word = QueryModel.objects.get(Q(id=word_id)) if QueryModel == Word: # need to annotate if word synwords = QueryModel.objects.annotate(txcount=Count('translations2nob')).filter(txcount__gt=0).filter(wordid=word.lemma) elif QueryModel == Wordnob: # No annotation, because synwords always has count of 1 synwords = QueryModel.objects.filter(wordid=word.lemma) # 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 for s in synwords: target_key = self.settings['transtype'][-3::] if type(s) == Word: translations = s.translations2(target_key).all() trs = [w.definition for w in translations] elif type(s) == Wordnob: translations = s.word_set.all() trs = [w.lemma for w in translations] for t in trs: tr_lemmas.append(t) correct = "" if type(word) == Word: trans_obj = word.translations2(self.settings['transtype']).all()[0] trans = trans_obj.definition if not correct: correct = trans_obj.definition elif type(word) == Wordnob: trans_obj = word.word_set.all()[0] 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. ;) # print repr(word), correct.encode('utf-8') form = (LeksaQuestion( self.settings['transtype'], word, correct, tr_lemmas, question_list, db_info['userans'], db_info['correct'], data, prefix=n)) return form, word.id