def demote_follower(request): resp, li, access = _list_access_from_post_pk(request, request.POST, need_edit = True) if resp: return resp try: access = ListAccess.objects.get(pk = int(request.POST['access_pk'])) except KeyError: return notification(request, 'No access key found.') except ValueError: return notification(request, 'Access key "%s" is not a valid format found.' % request.POST['access_pk']) except ListAccess.DoesNotExist: return notification(request, 'Access not found for key %s.' % request.POST['access_pk']) if access.access == ListAccess.EDIT: if ListAccess.objects.filter(translations_list = li, access = ListAccess.EDIT).count() <= 1: return notification(request, 'You cannot remove the last editor from a list.') access.access = ListAccess.VIEW access.save() add_message(request, INFO, '"%s" was demoted to a follower of the list.' % access.learner) elif access.access == ListAccess.VIEW: if li.public: return notification(request, 'You cannot remove followers from a public list.') access.delete() add_message(request, INFO, '"%s" was removed from the list.' % access.learner) else: raise AssertionError('unknown access state') return redirect(request.POST['next'] or reverse('list_followers', kwargs = {'pk': li.pk, 'slug': li.slug}))
def delete_phrase(request): try: phrase = Phrase.objects.get(pk = int(request.POST['pk'])) except (KeyError, ValueError, Phrase.DoesNotExist): return notification(request, 'The phrase you were looking for was not found; the submitted data may be invalid.') if not (phrase.public_edit or phrase.learner == request.user): return notification(request, 'You don\'t have permission to delete this phrase.') phrase.delete() request.user.need_active_update = True request.user.save() return redirect(reverse('user_lists'))
def remove_translation(request): resp, li, access = _list_access_from_post_pk(request, request.POST, need_edit = True) if resp: return resp try: translation = Translation.objects.get(pk = int(request.POST['trans_pk'])) except KeyError: return notification(request, 'No translation key found.') except ValueError: return notification(request, 'Translation key "%s" is not a valid format found.' % request.POST['trans_pk']) except TranslationsList.DoesNotExist: return notification(request, 'Translation not found for key %s.' % request.POST['trans_pk']) li.translations.remove(translation) add_message(request, INFO, '"%s" was removed from the list.' % translation) return redirect(request.POST['next'] or li.get_absolute_url())
def show_list(request, translations_list, slug = None): access_instance = None try: # learner = request.user should work here, but it doesn't, so use request.user.id as a kind of hack # http://stackoverflow.com/questions/15878860/int-argument-must-be-a-string-or-a-number-not-simplelazyobject access_instance = ListAccess.objects.get(translations_list = translations_list, learner = request.user.id) except ListAccess.DoesNotExist: if not translations_list.public: return notification(request, 'No access for list "%s".' % translations_list) translations = translations_list.translations.all() paginator = Paginator(translations, 50) page = request.GET.get('page', 1) try: items = paginator.page(page) except PageNotAnInteger: return redirect('%s?page=1' % request.path) except EmptyPage: return redirect('%s?page=%d' % (request.path, paginator.num_pages)) return render(request, 'show_list.html', { 'list': translations_list, 'access': access_instance, 'editable': access_instance.editable if access_instance else False, 'items': items, 'nearby_pages': _nearby_pages(items), })
def add_translation_vote(request): try: translation = Translation.objects.get(pk=int(request.POST['trans_pk'])) votes = TranslationVote.objects.filter(learner=request.user, translation=translation) up = bool(int(request.POST['up'])) except (KeyError, ValueError, Translation.DoesNotExist): return notification( request, message= 'The submitted data was not valid - translation and/or vote type were specified wrongly or not at all.' ) if votes: vote = votes[0] if vote.up == up: vote.delete() add_message(request, INFO, 'Your vote for "%s" was removed.' % (translation.text)) else: vote.up = up vote.save() add_message( request, INFO, 'Your vote for "%s" was changed from %s to %s.' % (translation.text, 'down' if up else 'up', 'up' if up else 'down')) else: vote = TranslationVote(translation=translation, up=up, learner=request.user) vote.save() add_message(request, INFO, 'Your vote for "%s" was added.' % (translation.text)) return redirect(request.POST['next'] or translation.phrase.get_absolute_url())
def delete_list(request): resp, li, access = _list_access_from_post_pk(request, request.POST, need_edit = True) if resp: return resp if ListAccess.objects.filter(translations_list = li, access = ListAccess.EDIT).count() > 1: return notification(request, 'There are other editors for this list. This means you cannot delete it. Unfollow it instead.') li.delete() return redirect(reverse('user_lists'))
def study_list_ask(request, translations_list, slug): """ Study a single list, mostly for demonstration purposes (so no account is needed). Doesn't save results, doesn't store state and easy to cheat with. """ translations = list(translations_list.translations.all()[:500]) shuffle(translations) for translation in translations: if translation.language == request.KNOWN_LANG: correct_lang_trans = translation.phrase.translations.filter(language = request.LEARN_LANG) elif translation.language == request.LEARN_LANG: correct_lang_trans = translation.phrase.translations.filter(language = request.KNOWN_LANG) else: continue """ Find a translation of this one that is in the correct langauge """ if not correct_lang_trans: continue shown, hidden = translation, correct_lang_trans[0] if random() > 0.5: shown, hidden = hidden, shown form = AnonStudyForm(None, initial = { 'shown': shown, 'hidden': hidden, }) return render(request, 'study_question.html', { 'shown': shown, 'hidden_language': hidden.language_disp(), 'form': form, 'list': translations_list, }) return notification(request, 'The list seems to contain no pairs of phrases in the languages you know and study.')
def study_demo(request): """ Redirect to a list to study (the first public one). """ lis = TranslationsList.objects.filter(public = True).order_by('pk') if not lis: return notification(request, 'There is no public list to study, sorry...') return redirect(reverse('study_list_ask', kwargs = {'pk': lis[0].pk, 'slug': lis[0].slug}))
def unfollow_list(request): #todo: only confirm if it's not public resp, li, access = _list_access_from_post_pk(request, request.POST, need_access = True, need_edit = False) if resp: return resp if access.access == ListAccess.EDIT: return notification(request, 'You are an editor for this list, you cannot unfollow it. First go to the followers page and hand over editorship. Or delete the list if you\re sure it\'s of no use to anyone.') access.delete() return redirect(request.POST['next'] or reverse('all_lists'))
def follow_list(request): resp, li, access = _list_access_from_post_pk(request, request.POST, need_access = False) if resp: return resp if access: return notification(request, 'You are already following the list "%s"' % li.name) ListAccess(access = ListAccess.VIEW, translations_list = li, learner = request.user).save() add_message(request, INFO, 'You are now following the list "%s".' % li) return redirect(request.POST['next'] or li.get_absolute_url())
def create_translation(request): if not request.POST['language'].strip(): add_message(request, ERROR, 'You need to provide the language for this phrase.') return redirect(request.POST['next'] or '/') form = CreateTranslationForm(request.POST) if form.is_valid(): phrase = form.cleaned_data['phrase'] if not (phrase.public_edit or phrase.learner == request.user): return notification(request, 'You don\'t have permission to add translations to this phrase.') if Translation.objects.filter(text = form.cleaned_data['text'], phrase = form.cleaned_data['phrase'], language = form.cleaned_data['language']): add_message(request, WARNING, 'This exact translation was already included and has been skipped.') else: add_message(request, INFO, 'Your translation "%s" has been added!' % form.cleaned_data['phrase']) form.save() request.user.need_active_update = True request.user.save() return redirect(request.POST['next'] or phrase.get_absolute_url()) return notification(request, 'The submitted phrase was not valid, sorry. %s' % ' '.join('%s: %s' % (field, msg) for field, msg in list(form.errors.items())))
def logout(request, next): if not request.method == 'POST': return redirect(to = reverse('home')) if not request.user.is_authenticated(): return redirect(to = reverse('login')) form = LogoutForm(data = request.POST) if form.is_valid(): auth_logout(request) add_message(request, INFO, 'You have been logged out. See you soon!') return redirect(to = request.POST['next'] or LOGIN_REDIRECT_URL) return notification(request, 'There was something wrong with the logout request. You have not been logged out.')
def promote_follower(request): resp, li, access = _list_access_from_post_pk(request, request.POST, need_edit = True) if resp: return resp try: access = ListAccess.objects.get(pk = int(request.POST['access_pk'])) except KeyError: return notification(request, 'No access key found.') except ValueError: return notification(request, 'Access key "%s" is not a valid format found.' % request.POST['access_pk']) except ListAccess.DoesNotExist: return notification(request, 'Access not found for key %s.' % request.POST['access_pk']) if access.access == ListAccess.EDIT: return notification(request, 'User %s already has all privileges.' % access.learner) elif access.access == ListAccess.VIEW: access.access = ListAccess.EDIT access.save() else: raise AssertionError('unknown access state') add_message(request, INFO, '"%s" was promoted to editor of the list.' % access.learner) return redirect(request.POST['next'] or reverse('list_followers', kwargs = {'pk': li.pk, 'slug': li.slug}))
def edit_phrase(request, phrase, next = None): if not (phrase.public_edit or phrase.learner == request.user): return notification(request, 'You don\'t have permission to edit this phrase.') phrase_form = EditPhraseForm(request.POST or None, instance = phrase) if phrase_form.is_valid(): phrase_form.save() """ No need for actives_update here, doesn't affect translations. """ return redirect(request.POST['next'] or phrase.get_absolute_url()) return render(request, 'edit_phrase.html', { 'phrase': phrase, 'phrase_form': phrase_form, 'next': next, })
def delete_translation(request): try: translation = Translation.objects.get(pk = int(request.POST['pk'])) except (KeyError, ValueError, Translation.DoesNotExist): return notification(request, 'The translation you were looking for was not found; the submitted data may be invalid.') next = request.POST['next'] or translation.phrase.get_absolute_url() if translation.score >= 0 and not translation.phrase.learner == request.user: add_message(request, ERROR, 'You can only remove translations that have a negative vote score (unless you\'re the owner)') else: add_message(request, INFO, 'The translation has been deleted') translation.delete() request.user.need_active_update = True request.user.save() return redirect(next)
def _list_access_from_post_pk(request, post, need_access = True, need_edit = True): """ :return: response, list, access (either the first or the other two are None) """ if not need_access: need_edit = False try: list_instance = TranslationsList.objects.get(pk = int(request.POST['pk'])) except KeyError: return notification(request, 'No list key found.'), None, None except ValueError: return notification(request, 'List key "%s" is not a valid format found.' % request.POST['pk']), None, None except TranslationsList.DoesNotExist: return notification(request, 'List not found for key %s.' % request.POST['pk']), None, None try: access_instance = ListAccess.objects.get(translations_list = list_instance, learner = request.user) except ListAccess.DoesNotExist: if need_access: return notification(request, 'No access for list "%s".' % list_instance), None, None else: access_instance = None if need_edit: if not access_instance.access == ListAccess.EDIT: return notification(request, 'You don\'t have edit access for list %s.' % list_instance), None, None return None, list_instance, access_instance
def edit_list(request, translations_list, slug = None, next = None): list_form = ListForm(request.POST or None, instance = translations_list) try: access_instance = ListAccess.objects.get(translations_list = translations_list, learner = request.user) except ListAccess.DoesNotExist: return notification(request, 'No access for list "%s".' % translations_list) access_form = ListAccessForm(request.POST or None, instance = access_instance) if access_instance.editable: if list_form.is_valid() and access_form.is_valid(): list_form.save() access_form.save() return redirect(request.POST['next'] or translations_list.get_absolute_url()) else: if list_form.is_valid() and access_form.is_valid(): access_form.save() return redirect(request.POST['next'] or translations_list.get_absolute_url()) return render(request, 'edit_list.html', { 'list_form': list_form, 'access_form': access_form, 'add': False, 'list': translations_list, 'access': access_instance, 'next': next, })
def import_hackingchinese_radicals(request): """ It would be much better to let this heavy work be done by a seperate process, one that is sufficiently detached to let Apache finish the request while the seperate process works. This would be much better for the user (now it loads a really long time) and performance (if a bunch of people upload files (more than Apache has worker processes) the server is unreachable until some are done. Implementing the better way would require not only a separate process (Celery?) but also some way to notify the user when done. """ #todo def parse(txt): data = [] for line in txt.splitlines(): if line: parts = line.split('\t') data.append(( '{0} [radical]'.format(parts[0]), # radical '{0}'.format(parts[4].strip(' ()')), # pinyin '{0} [radical] e.g. {1} ; note: {2}'.format(parts[3], parts[5], parts[6]), # definition )) return data @transaction.atomic def make_list(data, learner, show_pinyin): if show_pinyin: li = TranslationsList(name = 'top 100 radicals (hackingchinese) show pinyin', public = True, language = CNY) else: li = TranslationsList(name = 'top 100 radicals (hackingchinese) hide pinyin', public = True, language = CNY) li.save() ListAccess(translations_list = li, learner = learner, access = ListAccess.EDIT).save() for radical, pinyin, definition in data: phrase = Phrase(learner = learner, public_edit = False) phrase.save() if show_pinyin: trans_cny = Translation(phrase = phrase, language = CNY, text = '%s %s' % (radical, pinyin)) trans_en = Translation(phrase = phrase, language = EN, text = definition) else: trans_cny = Translation(phrase = phrase, language = CNY, text = '%s' % radical) trans_en = Translation(phrase = phrase, language = EN, text = '%s ; %s' % (pinyin, definition)) trans_cny.save() trans_en.save() li.translations.add(trans_cny) return li form = ImportForm(request.POST or None, request.FILES or None) if form.is_valid(): parse(form.get_content()) try: data = parse(form.get_content()) except Exception: return notification(request, 'Sorry, there was a problem parsing this file.') """ Make the first list, which shows Pinyin """ if not len(data): return notification(request, 'No data found') list_show_pinyin = make_list(data = data, learner = request.user, show_pinyin = True) list_hide_pinyin = make_list(data = data, learner = request.user, show_pinyin = False) return notification(request, 'Succesfully imported %d phrases! See the lists with <a href="%s">visible</a> and <a href="%s">hidden</a> pinyin.' % ( list_show_pinyin.translations.count(), reverse('show_list', kwargs = {'pk': list_show_pinyin.pk}), reverse('show_list', kwargs = {'pk': list_hide_pinyin.pk}), )) return render(request, 'import_form.html', { 'message': 'Import the top 100 most common radicals from <a href="http://www.hackingchinese.com/kickstart-your-character-learning-with-the-100-most-common-radicals/">hackingchinese.com</a>.', 'form': form, })
def study(request): #todo: show the last X results while studying (easy with Result) learner = request.user result_form = SolutionForm(request.POST) if learner.study_state == Learner.ASKING and 'solution' in request.POST: """ The user submitted a solution. """ if not result_form.is_valid(): add_message(request, ERROR, 'Could not find or understand the answer, sorry. Sending back to question.') return redirect(reverse('study_ask')) learner.study_answer = result_form.cleaned_data['solution'].strip() if learner.study_answer == learner.study_hidden.text.strip() and not request.POST.get('idk', '').strip() == 'idk': #todo: also check other languages in the future maybe """ Update the score (so it can be set to 'verified') but only go on to next card when user click 'go on'. """ update_score(learner, Result.CORRECT, verified = True) learner.study_state = Learner.JUDGED else: learner.study_state = Learner.REVEALED if request.POST.get('idk', '').strip() == 'idk': update_score(learner, Result.INCORRECT, verified = True) learner.study_state = Learner.JUDGED learner.save() if learner.study_state in [Learner.REVEALED, Learner.JUDGED] and 'result' in request.POST: """ The user judged the result, process it and go to the next question. """ result_map = {'correct': Result.CORRECT, 'notquite': Result.CLOSE, 'incorrect': Result.INCORRECT} try: result = result_map[request.POST['result']] except KeyError: return notification(request, 'The result you submitted, "%s", was not of expected format.' % request.POST['result']) if learner.study_state == Learner.REVEALED: update_score(learner, result) learner.study_shown = learner.study_hidden = None learner.study_answer = '' learner.phrase_index += 1 learner.study_state = Learner.ASKING learner.save() """ Skip the judged page; set to asking and match later. """ if learner.study_state in [Learner.REVEALED, Learner.JUDGED]: """ Show the solution. """ #correct = learner.study_answer == learner.study_hidden.text.strip() judge = not learner.study_state == Learner.JUDGED# else learner.study_state == Learner.REVEALED return render(request, 'study_result.html', { 'hidden': learner.study_hidden, 'shown': learner.study_shown, 'correct': learner.study_answer == learner.study_hidden.text.strip(), 'judge': judge, 'answer': learner.study_answer, 'result_form': result_form, 'list': None, }) if learner.study_state == Learner.NOTHING: learner.study_state = Learner.ASKING learner.save() if learner.study_state == Learner.ASKING: """ Since there's no solution in POST, the user just wants to see the question. """ msgs = [] if not learner.study_shown or not learner.study_hidden: add_more_active_phrases(learner = learner, lang = request.LEARN_LANG, msgs = msgs) update_learner_actives(learner = learner) try: learner.study_active, learner.study_hidden, learner.study_shown, msgs = get_next_question( learner = learner, known_language = request.KNOWN_LANG, learn_language = request.LEARN_LANG) except ActiveTranslation.DoesNotExist: add_message(request, ERROR, 'There are no phrases on your lists which are available in both your known and study languages. Please select some.') return redirect(to = reverse('list_activities')) learner.save() for lvl, txt in msgs: add_message(request, lvl, txt) form = SolutionForm(None) active_lists = ListAccess.objects.filter(active = True, learner = request.user).order_by('-priority') return render(request, 'study_question.html', { 'shown': learner.study_shown, 'hidden_language': learner.study_hidden.language_disp(), 'form': form, 'list': None, 'active_lists': active_lists, }) raise Exception('nothing matched')
def delete_translation_comment(request): return notification(request, 'Not implemented')
def import_hackingchinese_radicals(request): """ It would be much better to let this heavy work be done by a seperate process, one that is sufficiently detached to let Apache finish the request while the seperate process works. This would be much better for the user (now it loads a really long time) and performance (if a bunch of people upload files (more than Apache has worker processes) the server is unreachable until some are done. Implementing the better way would require not only a separate process (Celery?) but also some way to notify the user when done. """ #todo def parse(txt): data = [] for line in txt.splitlines(): if line: parts = line.split('\t') data.append(( '{0} [radical]'.format(parts[0]), # radical '{0}'.format(parts[4].strip(' ()')), # pinyin '{0} [radical] e.g. {1} ; note: {2}'.format( parts[3], parts[5], parts[6]), # definition )) return data @transaction.atomic def make_list(data, learner, show_pinyin): if show_pinyin: li = TranslationsList( name='top 100 radicals (hackingchinese) show pinyin', public=True, language=CNY) else: li = TranslationsList( name='top 100 radicals (hackingchinese) hide pinyin', public=True, language=CNY) li.save() ListAccess(translations_list=li, learner=learner, access=ListAccess.EDIT).save() for radical, pinyin, definition in data: phrase = Phrase(learner=learner, public_edit=False) phrase.save() if show_pinyin: trans_cny = Translation(phrase=phrase, language=CNY, text='%s %s' % (radical, pinyin)) trans_en = Translation(phrase=phrase, language=EN, text=definition) else: trans_cny = Translation(phrase=phrase, language=CNY, text='%s' % radical) trans_en = Translation(phrase=phrase, language=EN, text='%s ; %s' % (pinyin, definition)) trans_cny.save() trans_en.save() li.translations.add(trans_cny) return li form = ImportForm(request.POST or None, request.FILES or None) if form.is_valid(): parse(form.get_content()) try: data = parse(form.get_content()) except Exception: return notification( request, 'Sorry, there was a problem parsing this file.') """ Make the first list, which shows Pinyin """ if not len(data): return notification(request, 'No data found') list_show_pinyin = make_list(data=data, learner=request.user, show_pinyin=True) list_hide_pinyin = make_list(data=data, learner=request.user, show_pinyin=False) return notification( request, 'Succesfully imported %d phrases! See the lists with <a href="%s">visible</a> and <a href="%s">hidden</a> pinyin.' % ( list_show_pinyin.translations.count(), reverse('show_list', kwargs={'pk': list_show_pinyin.pk}), reverse('show_list', kwargs={'pk': list_hide_pinyin.pk}), )) return render( request, 'import_form.html', { 'message': 'Import the top 100 most common radicals from <a href="http://www.hackingchinese.com/kickstart-your-character-learning-with-the-100-most-common-radicals/">hackingchinese.com</a>.', 'form': form, })