def edit_dt_choice_combinations(request, poll_url): current_poll = get_object_or_404(Poll, url=poll_url) if not current_poll.can_edit(request.user): messages.error(request, _("You are not allowed to edit this Poll.")) return redirect('poll', poll_url) tz_activate(current_poll.timezone_name) if request.method == 'POST': # getlist does not raise an exception if datetimes[] is not in request.POST chosen_combinations = request.POST.getlist('datetimes[]') chosen_times = [] new_choices = [] old_choices = [] choices = current_poll.choice_set.all() # List of the Old Ids, used for detection what has to be deleted old_choices_ids = [c.pk for c in choices] # parse datetime objects of chosen combinations for combination in chosen_combinations: try: tz = timezone(current_poll.timezone_name) timestamp = parse_datetime(combination) if timestamp: chosen_times.append(tz.localize(timestamp)) else: messages.error( request, _("There was en error interpreting the provided dates and times" )) return redirect('poll_editDTChoiceDate', current_poll.url) except ValueError: # at least one invalid time/date has been specified. Redirect to first step # TODO: error message spezifizierne messages.error( request, _("There was en error interpreting the provided dates and times" )) return redirect('poll_editDTChoiceDate', current_poll.url) # Search for already existing Choices for i, date_time in enumerate(sorted(chosen_times)): choice_obj = current_poll.choice_set.filter(date=date_time) if choice_obj: old_choices_ids.remove(choice_obj[0].pk) choice_obj[0].sort_key = i choice_obj[0].deleted = False # Mark as not deleted old_choices.append(choice_obj[0]) else: new_choices.append( Choice(date=date_time, poll=current_poll, sort_key=i)) # Save new choices to database, Update/Delete old ones with transaction.atomic(): # Save the new Choices Choice.objects.bulk_create(new_choices) for choice in old_choices: choice.save() Choice.objects.filter(pk__in=old_choices_ids).update(deleted=True) return redirect('poll', current_poll.url) return redirect('poll_editDTChoiceDate', current_poll.url)
def edit_dt_choice_time(request, poll_url): """ :param request: :param poll_url: url of poll Takes several times as the user's input and checks the validity. If the data is valid, the user is directed to the combinations-site, to which all possible combinations of dates and times are passed on. If the dates are missing, the user is directed back to the date-input-site. If the times are missing, the user is directed back to the time-input-site. """ current_poll = get_object_or_404(Poll, url=poll_url) if not current_poll.can_edit(request.user): messages.error(request, _("You are not allowed to edit this Poll.")) return redirect('poll', poll_url) tz_activate(current_poll.timezone_name) initial = { 'dates': ','.join( date_format(localtime(c.date), format='Y-m-d') for c in current_poll.choice_set.order_by('sort_key')), 'times': ','.join( set( list( date_format(localtime(c.date), format='H:i') for c in current_poll.choice_set.order_by('sort_key')))), } if request.method == 'POST': form = DTChoiceCreationTimeForm(request.POST, initial=initial) if form.is_valid(): times = form.cleaned_data['times'].split(',') dates = form.cleaned_data['dates'].split(',') return TemplateResponse( request, "poll/dt_choice_creation_combinations.html", { 'times': times, 'dates': dates, 'poll': current_poll, 'page': 'Choices', 'step': 3, }) elif form.cleaned_data['dates'] != "": return TemplateResponse(request, "poll/dt_choice_creation_time.html", { 'time': form, 'poll': current_poll, 'page': 'Choices', 'step': 2, }) return redirect('poll_editDTChoiceDate', current_poll.url)
def edit_dt_choice_date(request, poll_url): """ :param request: :param poll_url: url of poll Takes several dates as the user's input and checks if it's valid. If the data is valid, the user is directed to the time-input-site. (The date is passed on as an argument) If the data is not valid, the user is directed back for correction. """ current_poll = get_object_or_404(Poll, url=poll_url) if not current_poll.can_edit(request.user): messages.error(request, _("You are not allowed to edit this Poll.")) return redirect('poll', poll_url) tz_activate(current_poll.timezone_name) initial = { 'dates': ','.join( set( list( date_format(localtime(c.date), format='Y-m-d') for c in current_poll.choice_set.order_by('sort_key')))), 'times': ','.join( set( list( date_format(localtime(c.date), format='H:i') for c in current_poll.choice_set.order_by('sort_key')))), } form = DTChoiceCreationDateForm(initial=initial) if request.method == 'POST': form = DTChoiceCreationDateForm(request.POST, initial=initial) if form.is_valid(): initial['dates'] = form.cleaned_data.get('dates') time = DTChoiceCreationTimeForm(initial=initial) return TemplateResponse(request, "poll/dt_choice_creation_time.html", { 'time': time, 'poll': current_poll, 'page': 'Choices', 'step': 2, }) return TemplateResponse( request, "poll/choice_creation_date.html", { 'new_choice': form, 'poll': current_poll, 'step': 1, 'page': 'Choices', 'is_dt_choice': True, })
def timetable(request, organization, project_id): organization = get_object_or_404(Organization, name=organization) project = get_object_or_404(Project, id=project_id) setting, _ = Setting.objects.get_or_create(user=request.user) timezone = pytz.timezone(str(setting.timezone)) if not project.is_member(request.user): return HttpResponseForbidden() tl_activate(setting.locale) tz_activate(timezone) time_records = TimeRecordTable(project.timerecord_set.all(), request=request) request.session['timetable.sort'] = request.GET.get( 'sort') or request.session.get('timetable.sort') time_records.order_by = request.session.get( 'timetable.sort') or '-end_time' RequestConfig(request, paginate={'per_page': 15}).configure(time_records) current_time = TimeRecord.round_time( datetime.now(timezone), timedelta(minutes=setting.timestamp_rounding)) form_add_record = TimeRecordForm( initial={"start_time": current_time.strftime('%Y-%m-%dT%H:%M')}) form_edit_record = TimeRecordForm( initial={"end_time": current_time.strftime('%Y-%m-%dT%H:%M')}) step_in_seconds = setting.timestamp_rounding * 60 form_add_record.fields['start_time'].widget.attrs.update( step=step_in_seconds) form_add_record.fields['end_time'].widget.attrs.update( step=step_in_seconds) form_edit_record.fields['start_time'].widget.attrs.update( step=step_in_seconds) form_edit_record.fields['end_time'].widget.attrs.update( step=step_in_seconds) context = dict(organization=organization, project=project, time_records=time_records, form_add_record=form_add_record, form_edit_record=form_edit_record) return render(request, 'tracker/timetable.html', context)
def details(request, organization, project_id): organization = get_object_or_404(Organization, name=organization) project = get_object_or_404(Project, id=project_id) setting, _ = Setting.objects.get_or_create(user=request.user) timezone = pytz.timezone(str(setting.timezone)) if not project.is_member(request.user): return HttpResponseForbidden() tl_activate(setting.locale) tz_activate(timezone) members = set(set(project.admins.all()) | set(project.editors.all())) recent_query = project.timerecord_set.order_by('-end_time')[:5] recent_changes = RecentRecordTable(recent_query, request=request) RequestConfig(request, paginate=False).configure(recent_changes) context = dict(organization=organization, project=project, members=members, recent_changes=recent_changes) return render(request, 'tracker/project/details.html', context)
def vote(request, poll_url, vote_id=None): """ :param request: :param poll_url: Url of poll :param vote_id: Optional the voteID to edit :return: Takes vote with comments as input and saves the vote along with all comments. """ current_poll = get_object_or_404(Poll, url=poll_url) error_msg = False deleted_choicevals = False if current_poll.due_date and current_poll.due_date < now(): messages.error( request, _("This Poll is past the due date, voting is no longer possible")) return redirect('poll', poll_url) if not current_poll.can_vote(request.user, request, vote_id is not None): if current_poll.require_login and not request.user.is_authenticated: return redirect_to_login(reverse('poll_vote', args=[poll_url])) else: return redirect('poll', poll_url) tz_activate(current_poll.get_tz_name(request.user)) if request.method == 'POST': vote_id = request.POST.get('vote_id', None) if vote_id: current_vote = get_object_or_404(Vote, pk=vote_id, poll=current_poll) if not current_vote.can_edit(request.user): # the vote belongs to an user and it is not the authenticated user return HttpResponseForbidden() # todo: better errorpage? else: current_vote = Vote(poll=current_poll) current_vote.date_created = now() current_vote.comment = request.POST.get('comment') if vote_id: # leave the name as it was pass elif 'anonymous' in request.POST: current_vote.name = 'Anonymous' elif request.user.is_authenticated: current_vote.name = request.user.get_displayname() current_vote.user = request.user if request.user.auto_watch: try: poll_watch = PollWatch(poll=current_poll, user=request.user) poll_watch.save() except IntegrityError: pass else: current_vote.name = request.POST.get('name').strip() if len(current_vote.name) > 80: messages.error( request, _("The Name is longer than the allowed name length of 80 characters" )) return redirect( 'poll', poll_url ) #todo: das macht keinen sinn, warum nicht verbessern? current_vote.anonymous = 'anonymous' in request.POST if not current_poll.anonymous_allowed and current_vote.anonymous: messages.error(request, _("Anonymous votes are not allowed for this poll.")) else: if current_vote.anonymous or current_vote.name: # prevent non-anonymous vote without name try: with transaction.atomic(): new_choices = [] current_vote.save() if request.user.is_authenticated: # check if this user was invited invitation = current_poll.invitation_set.filter( user=request.user) if invitation: invitation = invitation[0] invitation.vote = current_vote invitation.save() for choice in current_poll.choice_set.all(): if str(choice.id) in request.POST and request.POST[ str(choice.id)].isdecimal(): choice_value = get_object_or_404( ChoiceValue, id=request.POST[str(choice.id)]) if not choice_value.deleted: new_choices.append( VoteChoice( value=choice_value, vote=current_vote, choice=choice, comment=request.POST.get( 'comment_{}'.format(choice.id)) or '')) else: deleted_choicevals = True else: if current_poll.vote_all and not choice.deleted: if not error_msg: # TODO: error_msg is used in other places here to, maybe use # deduplication for messages? # https://stackoverflow.com/questions/23249807/django-remove-duplicate # -messages-from-storage messages.error( request, _('Due to the the configuration of this poll, ' 'you have to fill all choices.')) error_msg = True if deleted_choicevals: error_msg = True messages.error( request, _('Value for choice does not exist. This is probably due to ' 'changes in the poll. Please correct your vote.' )) if not error_msg: if vote_id: VoteChoice.objects.filter( vote=current_vote).delete() # todo: nochmal prüfen ob das wirjklich das tut was es soll, also erst alles löschen und dann neu anlegen # todo eventuell eine transaktion drum machen? wegen falls das eventuell dazwischen abbricht? else: for current_watch in current_poll.pollwatch_set.all( ): current_watch.send(request=request, vote=current_vote) VoteChoice.objects.bulk_create(new_choices) messages.success(request, _('Vote has been recorded')) return redirect('poll', poll_url) else: raise IntegrityError( "An Error while saving the Vote occurred, see message" ) except IntegrityError as e: # Nothing todo as the main point in this exception is the database rollback pass else: messages.error( request, _('You need to either provide a name or post an anonymous vote.' )) # no/invalid POST: show the dialog matrix = current_poll.get_choice_group_matrix(get_current_timezone()) choices = [] comments = [] choice_votes = [] if vote_id: current_vote = get_object_or_404(Vote, pk=vote_id) else: current_vote = Vote() choices_orig = current_poll.choice_set.filter( deleted=False).order_by('sort_key') for choice in choices_orig: cur_comment = "" value = None if request.method == 'POST': if str(choice.id) in request.POST: value = get_object_or_404(ChoiceValue, id=request.POST[str(choice.id)]) else: value = None cur_comment = request.POST.get('comment_{}'.format( choice.id)) or '' elif vote_id: # If we want to edit an vote find the selected fields vote_choice = VoteChoice.objects.filter(vote=vote_id, choice=choice.id) if vote_choice: # append the found values cur_comment = vote_choice[0].comment value = vote_choice[0].value choices.append(choice) comments.append(cur_comment) choice_votes.append(value) return TemplateResponse( request, 'poll/vote_creation.html', { 'poll': current_poll, 'matrix': matrix, 'matrix_len': len(matrix[0]), 'choices_matrix': zip(matrix, choices, comments, choice_votes), 'choices': current_poll.choice_set.all(), 'choices_matrix_len': len(choices), 'values': current_poll.choicevalue_set.filter(deleted=False).all(), 'page': 'Vote', 'current_vote': current_vote, 'timezone_warning': (request.user.is_authenticated and current_poll.get_tz_name(request.user) != request.user.timezone), 'choice_values': ChoiceValue.objects.filter(poll=current_poll) })
def edit_date_choice(request, poll_url): """ :param request: :param poll_url: url of poll Takes several dates as the user's input und checks the validity. If the input is valid, for every given date a choice is created and saved. The user is directed to the poll's site. If the input is not valid, the user is directed back for correction. """ current_poll = get_object_or_404(Poll, url=poll_url) if not current_poll.can_edit(request.user, request): return redirect('poll', poll_url) tz_activate('UTC') initial = { 'dates': ','.join( set( list( date_format(localtime(c.date), format='Y-m-d') for c in current_poll.choice_set.order_by('sort_key')))), } if request.method == 'POST': form = DateChoiceCreationForm(request.POST, initial=initial) if form.is_valid(): choices = current_poll.choice_set.all() # List of the Old Ids, used for detection what has to be deleted old_choices_ids = [c.pk for c in choices] new_choices = [] old_choices = [] dates = [] error = False # clean the data for choice in form.cleaned_data['dates'].split(","): try: tz = timezone('UTC') parsed_date = parse_datetime('{} 0:0'.format(choice)) if parsed_date: date = tz.localize(parsed_date) dates.append(date) else: error = True messages.error( _("There was en error interpreting the provided dates and times" )) except ValueError: # This will most likely only happen with users turning of JS error = True messages.error( _("There was en error interpreting the provided dates and times" )) if not error: for i, datum in enumerate(sorted(dates)): choice_objs = Choice.objects.filter(poll=current_poll, date=datum) if choice_objs: choice_obj = choice_objs[0] old_choices_ids.remove(choice_obj.pk) choice_obj.sort_key = i choice_obj.deleted = False old_choices.append(choice_obj) else: new_choices.append( Choice(text="", date=datum, poll=current_poll, sort_key=i)) with transaction.atomic(): Choice.objects.bulk_create(new_choices) for choice in old_choices: choice.save() Choice.objects.filter(pk__in=old_choices_ids).update( deleted=True) return redirect('poll', poll_url) else: form = DateChoiceCreationForm(initial=initial) return TemplateResponse( request, "poll/choice_creation_date.html", { 'poll': current_poll, 'new_choice': form, 'page': 'Choices', 'is_dt_choice': False, })
def poll(request, poll_url): """ :param request :param poll_url: url of poll Displays for a given poll its fields along with all possible choices, all votes and all its comments. """ current_poll = get_object_or_404(Poll, url=poll_url) tz_activate(current_poll.get_tz_name(request.user)) poll_votes = Vote.objects.filter(poll=current_poll).select_related('user') if current_poll.sorting == Poll.ResultSorting.NAME: poll_votes = poll_votes.order_by('name') elif current_poll.sorting == Poll.ResultSorting.DATE: poll_votes = poll_votes.order_by('date_created') # prefetch_related('votechoice_set').select_releated() #TODO (Prefetch objekt nötig, wie ist der reverse join name wirklich? matrix = transpose( current_poll.get_choice_group_matrix(get_current_timezone())) # aggregate stats for all columns stats = Choice.objects.filter( poll=current_poll, deleted=False).order_by('sort_key').annotate( score=Sum('votechoice__value__weight')).values( 'score', 'id', 'text') votes_count = poll_votes.count() invitations = current_poll.invitation_set.filter(vote=None) # The next block is limiting the visibility of the results summary = True if current_poll.current_user_is_owner( request) and current_poll.show_results != "complete": messages.info( request, _("You can see the results because you are the owner of the Poll")) else: if current_poll.show_results in ("summary", "never"): if request.user.is_authenticated: poll_votes = poll_votes.filter(user=request.user) invitations = invitations.filter(user=request.user) else: poll_votes = [] invitations = [] messages.info( request, _("No individual results are shown due to Poll settings")) elif current_poll.show_results in ("summary after vote", "complete after vote") \ and (request.user.is_anonymous or not poll_votes.filter(Q(user=request.user))): poll_votes = [] messages.info( request, _("Results are only sown after (authenticated) Voting")) summary = False elif current_poll.show_results == "summary after vote": poll_votes = poll_votes.filter(user=request.user) messages.info( request, _("Only the Summary is shown due to the Poll settings")) if current_poll.show_results == "never": summary = False if not summary: messages.info( request, _("The summary is not shown due to the config of the Poll")) choices = Choice.objects.filter( poll=current_poll, deleted=0).select_related('poll').order_by('sort_key') vote_idx = {vote.id: i for (i, vote) in enumerate(poll_votes)} choice_idx = {choice.id: (i, choice) for (i, choice) in enumerate(choices)} vote_choice_matrix = [[None] * len(choice_idx) for bla in vote_idx] for vote_choice in VoteChoice.objects.filter( vote__poll=current_poll, choice__deleted=0).select_related('value'): if vote_choice.vote_id in vote_idx: x = vote_idx[vote_choice.vote_id] y, choice = choice_idx[vote_choice.choice_id] vote_choice_matrix[x][y] = { 'comment': vote_choice.comment, 'value': vote_choice.value, 'choice': choice } # aggregate stats for the different Choice_Values per column stats2 = Choice.objects.filter( poll=current_poll, deleted=False).order_by('sort_key').annotate( count=Count('votechoice__value__color')).values( 'count', 'id', 'votechoice__value__icon', 'votechoice__value__color', 'votechoice__value__title', 'votechoice__value__deleted') # # use average for stats stats = [{ 'score': (stat['score'] / Decimal(votes_count) if votes_count > 0 else 0) if stat['score'] is not None else None, 'count': stat['score'], 'text': stat, 'choices': [{ 'count': stat2['count'], 'color': stat2['votechoice__value__color'], 'icon': stat2['votechoice__value__icon'], 'deleted': stat2['votechoice__value__deleted'], 'title': stat2['votechoice__value__title'] } for stat2 in stats2 if stat2['id'] == stat['id'] and stat2['votechoice__value__color'] != None], } for stat in stats] if request.user.is_authenticated: # warn the user if the Timezone is not the same on the Poll and in his settings different_timezone = current_poll.timezone_name != request.user.timezone if current_poll.use_user_timezone and different_timezone: messages.info( request, _("This poll was transferred from {} to your local timezone {}" .format(current_poll.timezone_name, request.user.timezone))) elif different_timezone: messages.warning( request, _("This poll has a different timezone ({}) than you.".format( current_poll.timezone_name))) deleted_choicevals_count = VoteChoice.objects.filter( choice__poll=current_poll, value__deleted=True).count() if deleted_choicevals_count > 0: messages.warning( request, _('Some votes contain deleted values. If you have already voted, please check your ' 'vote.')) max_score = None if stats and votes_count > 0: max_score_list = [ val['score'] for val in stats if val['score'] is not None ] if max_score_list: max_score = max(max_score_list) return TemplateResponse( request, "poll/poll.html", { 'poll': current_poll, 'matrix': matrix, 'choices_matrix': zip(matrix, current_poll.choice_set.all()), 'page': '', 'votes': zip(poll_votes, vote_choice_matrix), 'stats': stats, 'max_score': max_score, 'invitations': invitations if current_poll.show_invitations else [], 'summary': summary, 'comment_form': CommentForm(), 'choice_values': ChoiceValue.objects.filter(poll=current_poll), })
def settings(request, poll_url): """ :param request: :param poll_url: :return: """ current_poll = get_object_or_404(Poll, url=poll_url) groups = None if request.user.is_authenticated: groups = Group.objects.filter(user=request.user) if not current_poll.can_edit(request.user, request): return redirect('poll', poll_url) user_error = "" user = current_poll.user.username if current_poll.user else "" if request.method == 'POST': old_timezone_name = current_poll.timezone_name form = PollSettingsForm(request.POST, instance=current_poll) if form.is_valid(): new_poll = form.save(commit=False) user = form.data.get('user', '') with transaction.atomic(): if user: try: user_obj = BitpollUser.objects.get(username=user) new_poll.user = user_obj except ObjectDoesNotExist: user_error = _("User {} not Found".format(user)) else: new_poll.user = None current_poll.choice_set.all() if not user_error: # change the Timezone in the Choices, date-polls are in UTC regardles of the timezone if old_timezone_name != new_poll.timezone_name and current_poll.type == 'datetime': new_timezone = timezone(new_poll.timezone_name) old_timezone = timezone(old_timezone_name) for choice in current_poll.choice_set.all(): choice.date = make_aware( make_naive(choice.date, old_timezone), new_timezone) choice.save() new_poll.save() messages.success(request, _('Settings have been changed')) return redirect('poll_settings', current_poll.url) else: user = form.cleaned_data.get('user', '') else: form = PollSettingsForm(instance=current_poll) # we activate the base timezone of this poll so the due date etc is showed in the correct way. tz_activate(current_poll.timezone_name) return TemplateResponse( request, 'poll/settings.html', { 'form': form, 'poll': current_poll, 'page': 'Settings', 'groups': groups, 'results': POLL_RESULTS, 'timezones': all_timezones, 'user_error': user_error, 'user_select': user, })
def send_notify(command: Command, user: User, repo_tag: RepositoryTag): """ ユーザーに通知を送信する """ if not user.is_active: return context = { "repo": str(repo_tag), "last_updated": repo_tag.last_updated, "url": repo_tag.get_url(), } webhook_type = user.get_webhook_type() message = None if webhook_type == WebhookType.SLACK: try: lang_activate(str(user.language_code)) tz_activate(pytz.timezone(user.timezone)) message = render_to_string( 'messages/update_notify_slack.txt', context).encode('UTF-8') finally: lang_activate(SYS_LANGUAGE_CODE) tz_deactivate() elif webhook_type == WebhookType.IFTTT: try: lang_activate(str(user.language_code)) tz_activate(pytz.timezone(user.timezone)) message = render_to_string( 'messages/update_notify_slack.txt', context).encode('UTF-8') finally: lang_activate(SYS_LANGUAGE_CODE) tz_deactivate() elif webhook_type == WebhookType.UNKNOWN: command.stdout.write(command.style.ERROR( f'User {user} has UNKNOWN URL.\n{user.webhook_url}' )) if webhook_type not in [WebhookType.UNKNOWN, WebhookType.NONE]: result = False try: if message: result = user.post_webhook(message) except Exception: command.stdout.write(command.style.ERROR(traceback.format_exc())) finally: if result: command.stdout.write(command.style.SUCCESS( f'Webhook notification was successfully.' f' "{repo_tag}", last_updated: {repo_tag.last_updated}' )) else: command.stdout.write(command.style.ERROR( f'Webhook notification was failed.' f' "{repo_tag}", last_updated: {repo_tag.last_updated}\n' f'message: {message}' )) if user.is_notify_to_email: result = False try: lang_activate(str(user.language_code)) tz_activate(pytz.timezone(user.timezone)) result = user.email_user( subject=render_to_string( 'messages/update_notify_subject.txt', context), message=render_to_string( 'messages/update_notify_email.html', context) ) except Exception: command.stdout.write(command.style.ERROR(traceback.format_exc())) finally: lang_activate(SYS_LANGUAGE_CODE) tz_deactivate() if result: command.stdout.write(command.style.SUCCESS( f'E-mail notification was successfully.' f' "{repo_tag}", last_updated: {repo_tag.last_updated}' )) else: command.stdout.write(command.style.ERROR( f'E-mail notification was failed.' f' "{repo_tag}", last_updated: {repo_tag.last_updated}\n' f'message: {message}' ))