<commit_before># -*- coding: utf-8 -*-
from datetime import datetime
from decimal import Decimal

from django.db import models
from django.db.models import Q
from django.db.models.signals import post_save

from django.contrib.auth.models import User

from symposion.proposals.models import ProposalBase
from symposion.schedule.models import Presentation

class ProposalScoreExpression(object):

    def as_sql(self, qn, connection=None):
        sql = "((3 * plus_one + plus_zero) - (minus_zero + 3 * minus_one))"
        return sql, []

    def prepare_database_save(self, unused):
        return self

class Votes(object):
    PLUS_ONE = "+1"
    PLUS_ZERO = "+0"
    MINUS_ZERO = u"−0"
    MINUS_ONE = u"−1"

    CHOICES = [
        (PLUS_ONE, u"+1 — Good proposal and I will argue for it to be accepted."),
        (PLUS_ZERO, u"+0 — OK proposal, but I will not argue for it to be accepted."),
        (MINUS_ZERO, u"−0 — Weak proposal, but I will not argue strongly against acceptance."),
        (MINUS_ONE, u"−1 — Serious issues and I will argue to reject this proposal."),
    ]
VOTES = Votes()

class ReviewAssignment(models.Model):
    AUTO_ASSIGNED_INITIAL = 0
    OPT_IN = 1
    AUTO_ASSIGNED_LATER = 2

    ORIGIN_CHOICES = [
        (AUTO_ASSIGNED_INITIAL, "auto-assigned, initial"),
        (OPT_IN, "opted-in"),
        (AUTO_ASSIGNED_LATER, "auto-assigned, later"),
    ]

    proposal = models.ForeignKey("proposals.ProposalBase")
    user = models.ForeignKey(User)

    origin = models.IntegerField(choices=ORIGIN_CHOICES)

    assigned_at = models.DateTimeField(default=datetime.now)
    opted_out = models.BooleanField()

    @classmethod
    def create_assignments(cls, proposal, origin=AUTO_ASSIGNED_INITIAL):
        speakers = [proposal.speaker] + list(proposal.additional_speakers.all())
        reviewers = User.objects.exclude(
            pk__in=[
                speaker.user_id
                for speaker in speakers
                if speaker.user_id is not None
            ]
        ).filter(
            groups__name="reviewers",
        ).filter(
            Q(reviewassignment__opted_out=False) | Q(reviewassignment=None)
        ).annotate(
            num_assignments=models.Count("reviewassignment")
        ).order_by(
            "num_assignments",
        )
        for reviewer in reviewers[:3]:
            cls._default_manager.create(
                proposal=proposal,
                user=reviewer,
                origin=origin,
            )

class ProposalMessage(models.Model):
    proposal = models.ForeignKey("proposals.ProposalBase", related_name="messages")
    user = models.ForeignKey(User)

    message = models.TextField()
    submitted_at = models.DateTimeField(default=datetime.now, editable=False)

    class Meta:
        ordering = ["submitted_at"]

class Review(models.Model):
    VOTES = VOTES

    proposal = models.ForeignKey("proposals.ProposalBase", related_name="reviews")
    user = models.ForeignKey(User)

    # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel
    # like some complicated encoding system.
    vote = models.CharField(max_length=2, blank=True, choices=VOTES.CHOICES)
    comment = models.TextField()
    submitted_at = models.DateTimeField(default=datetime.now, editable=False)

    def save(self, **kwargs):
        if self.vote:
            vote, created = LatestVote.objects.get_or_create(
                proposal = self.proposal,
                user = self.user,
                defaults = dict(
                    vote = self.vote,
                    submitted_at = self.submitted_at,
                )
            )
            if not created:
                LatestVote.objects.filter(pk=vote.pk).update(vote=self.vote)
                self.proposal.result.update_vote(self.vote, previous=vote.vote)
            else:
                self.proposal.result.update_vote(self.vote)
        super(Review, self).save(**kwargs)

    def delete(self):
        model = self.__class__
        user_reviews = model._default_manager.filter(
            proposal=self.proposal,
            user=self.user,
        )
        try:
            # find the latest review
            latest = user_reviews.exclude(pk=self.pk).order_by("-submitted_at")[0]
        except IndexError:
            # did not find a latest which means this must be the only one.
            # treat it as a last, but delete the latest vote.
            self.proposal.result.update_vote(self.vote, removal=True)
            lv = LatestVote.objects.filter(proposal=self.proposal, user=self.user)
            lv.delete()
        else:
            # handle that we've found a latest vote
            # check if self is the lastest vote
            if self == latest:
                # self is the latest review; revert the latest vote to the
                # previous vote
                previous = user_reviews.filter(submitted_at__lt=self.submitted_at).order_by("-submitted_at")[0]
                self.proposal.result.update_vote(self.vote, previous=previous.vote, removal=True)
                lv = LatestVote.objects.filter(proposal=self.proposal, user=self.user)
                lv.update(
                    vote=previous.vote,
                    submitted_at=previous.submitted_at,
                )
            else:
                # self is not the latest review so we just need to decrement
                # the comment count
                self.proposal.result.comment_count = models.F("comment_count") - 1
                self.proposal.result.save()
        # in all cases we need to delete the review; let's do it!
        super(Review, self).delete()

    def css_class(self):
        return {
            self.VOTES.PLUS_ONE: "plus-one",
            self.VOTES.PLUS_ZERO: "plus-zero",
            self.VOTES.MINUS_ZERO: "minus-zero",
            self.VOTES.MINUS_ONE: "minus-one",
        }[self.vote]

    @property
    def section(self):
        return self.proposal.kind.section.slug

class LatestVote(models.Model):
    VOTES = VOTES

    proposal = models.ForeignKey("proposals.ProposalBase", related_name="votes")
    user = models.ForeignKey(User)

    # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel
    # like some complicated encoding system.
    vote = models.CharField(max_length=2, choices=VOTES.CHOICES)
    submitted_at = models.DateTimeField(default=datetime.now, editable=False)

    class Meta:
        unique_together = [("proposal", "user")]

    def css_class(self):
        return {
            self.VOTES.PLUS_ONE: "plus-one",
            self.VOTES.PLUS_ZERO: "plus-zero",
            self.VOTES.MINUS_ZERO: "minus-zero",
            self.VOTES.MINUS_ONE: "minus-one",
        }[self.vote]

class ProposalResult(models.Model):
    proposal = models.OneToOneField("proposals.ProposalBase", related_name="result")
    score = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.00"))
    comment_count = models.PositiveIntegerField(default=0)
    vote_count = models.PositiveIntegerField(default=0)
    plus_one = models.PositiveIntegerField(default=0)
    plus_zero = models.PositiveIntegerField(default=0)
    minus_zero = models.PositiveIntegerField(default=0)
    minus_one = models.PositiveIntegerField(default=0)
    accepted = models.NullBooleanField(choices=[
        (True, "accepted"),
        (False, "rejected"),
        (None, "undecided"),
    ], default=None)
    status = models.CharField(max_length=20, choices=[
        ("accepted", "accepted"),
        ("rejected", "rejected"),
        ("undecided", "undecided"),
        ("standby", "standby"),
    ], default="undecided")

    @classmethod
    def full_calculate(cls):
        for proposal in ProposalBase.objects.all():
            result, created = cls._default_manager.get_or_create(proposal=proposal)
            result.comment_count = Review.objects.filter(proposal=proposal).count()
            result.vote_count = LatestVote.objects.filter(proposal=proposal).count()
            result.plus_one = LatestVote.objects.filter(
                proposal = proposal,
                vote = VOTES.PLUS_ONE
            ).count()
            result.plus_zero = LatestVote.objects.filter(
                proposal = proposal,
                vote = VOTES.PLUS_ZERO
            ).count()
            result.minus_zero = LatestVote.objects.filter(
                proposal = proposal,
                vote = VOTES.MINUS_ZERO
            ).count()
            result.minus_one = LatestVote.objects.filter(
                proposal = proposal,
                vote = VOTES.MINUS_ONE
            ).count()
            result.save()
            cls._default_manager.filter(pk=result.pk).update(score=ProposalScoreExpression())

    def update_vote(self, vote, previous=None, removal=False):
        mapping = {
            VOTES.PLUS_ONE: "plus_one",
            VOTES.PLUS_ZERO: "plus_zero",
            VOTES.MINUS_ZERO: "minus_zero",
            VOTES.MINUS_ONE: "minus_one",
        }
        if previous:
            if previous == vote:
                return
            if removal:
                setattr(self, mapping[previous], models.F(mapping[previous]) + 1)
            else:
                setattr(self, mapping[previous], models.F(mapping[previous]) - 1)
        else:
            if removal:
                self.vote_count = models.F("vote_count") - 1
            else:
                self.vote_count = models.F("vote_count") + 1
        if removal:
            setattr(self, mapping[vote], models.F(mapping[vote]) - 1)
            self.comment_count = models.F("comment_count") - 1
        else:
            setattr(self, mapping[vote], models.F(mapping[vote]) + 1)
            self.comment_count = models.F("comment_count") + 1
        self.save()
        model = self.__class__
        model._default_manager.filter(pk=self.pk).update(score=ProposalScoreExpression())

class Comment(models.Model):
    proposal = models.ForeignKey("proposals.ProposalBase", related_name="comments")
    commenter = models.ForeignKey(User)
    text = models.TextField()

    # Or perhaps more accurately, can the user see this comment.
    public = models.BooleanField(choices=[
        (True, "public"),
        (False, "private"),
    ])
    commented_at = models.DateTimeField(default=datetime.now)

class NotificationTemplate(models.Model):

    label = models.CharField(max_length=100)
    from_address = models.EmailField()
    subject = models.CharField(max_length=100)
    body = models.TextField()

class ResultNotification(models.Model):

    proposal = models.ForeignKey("proposals.ProposalBase", related_name="notifications")
    template = models.ForeignKey(NotificationTemplate, null=True, blank=True, on_delete=models.SET_NULL)
    timestamp = models.DateTimeField(default=datetime.now)
    to_address = models.EmailField()
    from_address = models.EmailField()
    subject = models.CharField(max_length=100)
    body = models.TextField()

    @property
    def email_args(self):
        return (self.subject, self.body, self.from_address, [self.to_address])

def promote_proposal(proposal):
    if hasattr(proposal, "presentation") and proposal.presentation:
        # already promoted
        presentation = proposal.presentation
    else:
        presentation = Presentation(
            title = proposal.title,
            description = proposal.description,
            abstract = proposal.abstract,
            speaker = proposal.speaker,
            section = proposal.section,
            proposal_base = proposal,
        )
        presentation.save()
        for speaker in proposal.additional_speakers.all():
            presentation.additional_speakers.add(speaker)
            presentation.save()

    return presentation

def unpromote_proposal(proposal):
    if hasattr(proposal, "presentation") and proposal.presentation:
        proposal.presentation.delete()

def accepted_proposal(sender, instance=None, **kwargs):
    if instance is None:
        return
    if instance.status == "accepted":
        promote_proposal(instance.proposal)
    else:
        unpromote_proposal(instance.proposal)
post_save.connect(accepted_proposal, sender=ProposalResult)
<commit_msg>Sending email to all speakers per proposal<commit_after># -*- coding: utf-8 -*-
from datetime import datetime
from decimal import Decimal

from django.db import models
from django.db.models import Q
from django.db.models.signals import post_save

from django.contrib.auth.models import User

from symposion.proposals.models import ProposalBase
from symposion.schedule.models import Presentation

class ProposalScoreExpression(object):

    def as_sql(self, qn, connection=None):
        sql = "((3 * plus_one + plus_zero) - (minus_zero + 3 * minus_one))"
        return sql, []

    def prepare_database_save(self, unused):
        return self

class Votes(object):
    PLUS_ONE = "+1"
    PLUS_ZERO = "+0"
    MINUS_ZERO = u"−0"
    MINUS_ONE = u"−1"

    CHOICES = [
        (PLUS_ONE, u"+1 — Good proposal and I will argue for it to be accepted."),
        (PLUS_ZERO, u"+0 — OK proposal, but I will not argue for it to be accepted."),
        (MINUS_ZERO, u"−0 — Weak proposal, but I will not argue strongly against acceptance."),
        (MINUS_ONE, u"−1 — Serious issues and I will argue to reject this proposal."),
    ]
VOTES = Votes()

class ReviewAssignment(models.Model):
    AUTO_ASSIGNED_INITIAL = 0
    OPT_IN = 1
    AUTO_ASSIGNED_LATER = 2

    ORIGIN_CHOICES = [
        (AUTO_ASSIGNED_INITIAL, "auto-assigned, initial"),
        (OPT_IN, "opted-in"),
        (AUTO_ASSIGNED_LATER, "auto-assigned, later"),
    ]

    proposal = models.ForeignKey("proposals.ProposalBase")
    user = models.ForeignKey(User)

    origin = models.IntegerField(choices=ORIGIN_CHOICES)

    assigned_at = models.DateTimeField(default=datetime.now)
    opted_out = models.BooleanField()

    @classmethod
    def create_assignments(cls, proposal, origin=AUTO_ASSIGNED_INITIAL):
        speakers = [proposal.speaker] + list(proposal.additional_speakers.all())
        reviewers = User.objects.exclude(
            pk__in=[
                speaker.user_id
                for speaker in speakers
                if speaker.user_id is not None
            ]
        ).filter(
            groups__name="reviewers",
        ).filter(
            Q(reviewassignment__opted_out=False) | Q(reviewassignment=None)
        ).annotate(
            num_assignments=models.Count("reviewassignment")
        ).order_by(
            "num_assignments",
        )
        for reviewer in reviewers[:3]:
            cls._default_manager.create(
                proposal=proposal,
                user=reviewer,
                origin=origin,
            )

class ProposalMessage(models.Model):
    proposal = models.ForeignKey("proposals.ProposalBase", related_name="messages")
    user = models.ForeignKey(User)

    message = models.TextField()
    submitted_at = models.DateTimeField(default=datetime.now, editable=False)

    class Meta:
        ordering = ["submitted_at"]

class Review(models.Model):
    VOTES = VOTES

    proposal = models.ForeignKey("proposals.ProposalBase", related_name="reviews")
    user = models.ForeignKey(User)

    # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel
    # like some complicated encoding system.
    vote = models.CharField(max_length=2, blank=True, choices=VOTES.CHOICES)
    comment = models.TextField()
    submitted_at = models.DateTimeField(default=datetime.now, editable=False)

    def save(self, **kwargs):
        if self.vote:
            vote, created = LatestVote.objects.get_or_create(
                proposal = self.proposal,
                user = self.user,
                defaults = dict(
                    vote = self.vote,
                    submitted_at = self.submitted_at,
                )
            )
            if not created:
                LatestVote.objects.filter(pk=vote.pk).update(vote=self.vote)
                self.proposal.result.update_vote(self.vote, previous=vote.vote)
            else:
                self.proposal.result.update_vote(self.vote)
        super(Review, self).save(**kwargs)

    def delete(self):
        model = self.__class__
        user_reviews = model._default_manager.filter(
            proposal=self.proposal,
            user=self.user,
        )
        try:
            # find the latest review
            latest = user_reviews.exclude(pk=self.pk).order_by("-submitted_at")[0]
        except IndexError:
            # did not find a latest which means this must be the only one.
            # treat it as a last, but delete the latest vote.
            self.proposal.result.update_vote(self.vote, removal=True)
            lv = LatestVote.objects.filter(proposal=self.proposal, user=self.user)
            lv.delete()
        else:
            # handle that we've found a latest vote
            # check if self is the lastest vote
            if self == latest:
                # self is the latest review; revert the latest vote to the
                # previous vote
                previous = user_reviews.filter(submitted_at__lt=self.submitted_at).order_by("-submitted_at")[0]
                self.proposal.result.update_vote(self.vote, previous=previous.vote, removal=True)
                lv = LatestVote.objects.filter(proposal=self.proposal, user=self.user)
                lv.update(
                    vote=previous.vote,
                    submitted_at=previous.submitted_at,
                )
            else:
                # self is not the latest review so we just need to decrement
                # the comment count
                self.proposal.result.comment_count = models.F("comment_count") - 1
                self.proposal.result.save()
        # in all cases we need to delete the review; let's do it!
        super(Review, self).delete()

    def css_class(self):
        return {
            self.VOTES.PLUS_ONE: "plus-one",
            self.VOTES.PLUS_ZERO: "plus-zero",
            self.VOTES.MINUS_ZERO: "minus-zero",
            self.VOTES.MINUS_ONE: "minus-one",
        }[self.vote]

    @property
    def section(self):
        return self.proposal.kind.section.slug

class LatestVote(models.Model):
    VOTES = VOTES

    proposal = models.ForeignKey("proposals.ProposalBase", related_name="votes")
    user = models.ForeignKey(User)

    # No way to encode "-0" vs. "+0" into an IntegerField, and I don't feel
    # like some complicated encoding system.
    vote = models.CharField(max_length=2, choices=VOTES.CHOICES)
    submitted_at = models.DateTimeField(default=datetime.now, editable=False)

    class Meta:
        unique_together = [("proposal", "user")]

    def css_class(self):
        return {
            self.VOTES.PLUS_ONE: "plus-one",
            self.VOTES.PLUS_ZERO: "plus-zero",
            self.VOTES.MINUS_ZERO: "minus-zero",
            self.VOTES.MINUS_ONE: "minus-one",
        }[self.vote]

class ProposalResult(models.Model):
    proposal = models.OneToOneField("proposals.ProposalBase", related_name="result")
    score = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal("0.00"))
    comment_count = models.PositiveIntegerField(default=0)
    vote_count = models.PositiveIntegerField(default=0)
    plus_one = models.PositiveIntegerField(default=0)
    plus_zero = models.PositiveIntegerField(default=0)
    minus_zero = models.PositiveIntegerField(default=0)
    minus_one = models.PositiveIntegerField(default=0)
    accepted = models.NullBooleanField(choices=[
        (True, "accepted"),
        (False, "rejected"),
        (None, "undecided"),
    ], default=None)
    status = models.CharField(max_length=20, choices=[
        ("accepted", "accepted"),
        ("rejected", "rejected"),
        ("undecided", "undecided"),
        ("standby", "standby"),
    ], default="undecided")

    @classmethod
    def full_calculate(cls):
        for proposal in ProposalBase.objects.all():
            result, created = cls._default_manager.get_or_create(proposal=proposal)
            result.comment_count = Review.objects.filter(proposal=proposal).count()
            result.vote_count = LatestVote.objects.filter(proposal=proposal).count()
            result.plus_one = LatestVote.objects.filter(
                proposal = proposal,
                vote = VOTES.PLUS_ONE
            ).count()
            result.plus_zero = LatestVote.objects.filter(
                proposal = proposal,
                vote = VOTES.PLUS_ZERO
            ).count()
            result.minus_zero = LatestVote.objects.filter(
                proposal = proposal,
                vote = VOTES.MINUS_ZERO
            ).count()
            result.minus_one = LatestVote.objects.filter(
                proposal = proposal,
                vote = VOTES.MINUS_ONE
            ).count()
            result.save()
            cls._default_manager.filter(pk=result.pk).update(score=ProposalScoreExpression())

    def update_vote(self, vote, previous=None, removal=False):
        mapping = {
            VOTES.PLUS_ONE: "plus_one",
            VOTES.PLUS_ZERO: "plus_zero",
            VOTES.MINUS_ZERO: "minus_zero",
            VOTES.MINUS_ONE: "minus_one",
        }
        if previous:
            if previous == vote:
                return
            if removal:
                setattr(self, mapping[previous], models.F(mapping[previous]) + 1)
            else:
                setattr(self, mapping[previous], models.F(mapping[previous]) - 1)
        else:
            if removal:
                self.vote_count = models.F("vote_count") - 1
            else:
                self.vote_count = models.F("vote_count") + 1
        if removal:
            setattr(self, mapping[vote], models.F(mapping[vote]) - 1)
            self.comment_count = models.F("comment_count") - 1
        else:
            setattr(self, mapping[vote], models.F(mapping[vote]) + 1)
            self.comment_count = models.F("comment_count") + 1
        self.save()
        model = self.__class__
        model._default_manager.filter(pk=self.pk).update(score=ProposalScoreExpression())

class Comment(models.Model):
    proposal = models.ForeignKey("proposals.ProposalBase", related_name="comments")
    commenter = models.ForeignKey(User)
    text = models.TextField()

    # Or perhaps more accurately, can the user see this comment.
    public = models.BooleanField(choices=[
        (True, "public"),
        (False, "private"),
    ])
    commented_at = models.DateTimeField(default=datetime.now)

class NotificationTemplate(models.Model):

    label = models.CharField(max_length=100)
    from_address = models.EmailField()
    subject = models.CharField(max_length=100)
    body = models.TextField()

class ResultNotification(models.Model):

    proposal = models.ForeignKey("proposals.ProposalBase", related_name="notifications")
    template = models.ForeignKey(NotificationTemplate, null=True, blank=True, on_delete=models.SET_NULL)
    timestamp = models.DateTimeField(default=datetime.now)
    to_address = models.EmailField()
    from_address = models.EmailField()
    subject = models.CharField(max_length=100)
    body = models.TextField()

    def recipients(self):
        yield self.to_address
        for speaker in self.proposal.speakers():
            yield speaker.email

    @property
    def email_args(self):
        return (self.subject, self.body, self.from_address, self.recipients())

def promote_proposal(proposal):
    if hasattr(proposal, "presentation") and proposal.presentation:
        # already promoted
        presentation = proposal.presentation
    else:
        presentation = Presentation(
            title = proposal.title,
            description = proposal.description,
            abstract = proposal.abstract,
            speaker = proposal.speaker,
            section = proposal.section,
            proposal_base = proposal,
        )
        presentation.save()
        for speaker in proposal.additional_speakers.all():
            presentation.additional_speakers.add(speaker)
            presentation.save()

    return presentation

def unpromote_proposal(proposal):
    if hasattr(proposal, "presentation") and proposal.presentation:
        proposal.presentation.delete()

def accepted_proposal(sender, instance=None, **kwargs):
    if instance is None:
        return
    if instance.status == "accepted":
        promote_proposal(instance.proposal)
    else:
        unpromote_proposal(instance.proposal)
post_save.connect(accepted_proposal, sender=ProposalResult)
<|endoftext|>


<commit_before>from datetime import datetime
import json

from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.template import loader, Context

from django.contrib.auth.decorators import login_required
from django.contrib.sites.models import Site

from symposion.schedule.forms import SlotEditForm
from symposion.schedule.models import Schedule, Day, Slot, Presentation
from symposion.schedule.timetable import TimeTable

def fetch_schedule(slug):
    qs = Schedule.objects.all()

    if slug is None:
        if qs.count() > 1:
            raise Http404()
        schedule = next(iter(qs), None)
        if schedule is None:
            raise Http404()
    else:
        schedule = get_object_or_404(qs, section__slug=slug)

    return schedule

def schedule_conference(request):

    schedules = Schedule.objects.filter(published=True, hidden=False)

    sections = []
    for schedule in schedules:
        days_qs = Day.objects.filter(schedule=schedule)
        days = [TimeTable(day) for day in days_qs]
        sections.append({
            "schedule": schedule,
            "days": days,
        })

    ctx = {
        "sections": sections,
    }
    return render(request, "schedule/schedule_conference.html", ctx)

def schedule_detail(request, slug=None):

    schedule = fetch_schedule(slug)
    if not schedule.published and not request.user.is_staff:
        raise Http404()

    days_qs = Day.objects.filter(schedule=schedule)
    days = [TimeTable(day) for day in days_qs]

    ctx = {
        "schedule": schedule,
        "days": days,
    }
    return render(request, "schedule/schedule_detail.html", ctx)

def schedule_list(request, slug=None):
    schedule = fetch_schedule(slug)

    presentations = Presentation.objects.filter(section=schedule.section)
    presentations = presentations.exclude(cancelled=True)

    ctx = {
        "schedule": schedule,
        "presentations": presentations,
    }
    return render(request, "schedule/schedule_list.html", ctx)

def schedule_list_csv(request, slug=None):
    schedule = fetch_schedule(slug)

    presentations = Presentation.objects.filter(section=schedule.section)
    presentations = presentations.exclude(cancelled=True).order_by("id")

    response = HttpResponse(mimetype="text/csv")
    if slug:
        file_slug = slug
    else:
        file_slug = "presentations"
    response["Content-Disposition"] = 'attachment; filename="%s.csv"' % file_slug

    response.write(loader.get_template("schedule/schedule_list.csv").render(Context({
        "presentations": presentations,

    })))
    return response

@login_required
def schedule_edit(request, slug=None):

    if not request.user.is_staff:
        raise Http404()

    schedule = fetch_schedule(slug)

    days_qs = Day.objects.filter(schedule=schedule)
    days = [TimeTable(day) for day in days_qs]
    ctx = {
        "schedule": schedule,
        "days": days,
    }
    return render(request, "schedule/schedule_edit.html", ctx)

@login_required
def schedule_slot_edit(request, slug, slot_pk):

    if not request.user.is_staff:
        raise Http404()

    slot = get_object_or_404(Slot, day__schedule__section__slug=slug, pk=slot_pk)

    if request.method == "POST":
        form = SlotEditForm(request.POST, slot=slot)
        if form.is_valid():
            save = False
            if "content_override" in form.cleaned_data:
                slot.content_override = form.cleaned_data["content_override"]
                save = True
            if "presentation" in form.cleaned_data:
                presentation = form.cleaned_data["presentation"]
                if presentation is None:
                    slot.unassign()
                else:
                    slot.assign(presentation)
            if save:
                slot.save()
        return redirect("schedule_edit", slug)
    else:
        form = SlotEditForm(slot=slot)
        ctx = {
            "slug": slug,
            "form": form,
            "slot": slot,
        }
        return render(request, "schedule/_slot_edit.html", ctx)

def schedule_presentation_detail(request, pk):

    presentation = get_object_or_404(Presentation, pk=pk)
    if presentation.slot:
        schedule = presentation.slot.day.schedule
    else:
        schedule = None

    ctx = {
        "presentation": presentation,
        "schedule": schedule,
    }
    return render(request, "schedule/presentation_detail.html", ctx)

def schedule_json(request):
    slots = Slot.objects.filter(
        day__schedule__published=True,
        day__schedule__hidden=False
    ).order_by("start")

    protocol = request.META.get('HTTP_X_FORWARDED_PROTO', 'http')
    data = []
    for slot in slots:
        slot_data = {
            "room": ", ".join(room["name"] for room in slot.rooms.values()),
            "rooms": [room["name"] for room in slot.rooms.values()],
            "start": datetime.combine(slot.day.date, slot.start).isoformat(),
            "end": datetime.combine(slot.day.date, slot.end).isoformat(),
            "duration": slot.length_in_minutes,
            "kind": slot.kind.label,
            "section": slot.day.schedule.section.slug,
        }
        if hasattr(slot.content, "proposal"):
            slot_data.update({
                "name": slot.content.title,
                "authors": [s.name for s in slot.content.speakers()],
                "contact": [
                    s.email for s in slot.content.speakers()
                ] if request.user.is_staff else ["redacted"],
                "abstract": slot.content.abstract.raw,
                "description": slot.content.description.raw,
                "content_href": "%s://%s%s" % (
                    protocol,
                    Site.objects.get_current().domain,
                    reverse("schedule_presentation_detail", args=[slot.content.pk])
                ),
            })
        else:
            slot_data.update({
                "name": slot.content_override.raw if slot.content_override else "Slot",
            })
        data.append(slot_data)

    return HttpResponse(
        json.dumps({'schedule': data}),
        content_type="application/json"
    )
<commit_msg>added cancelled element to json, used taavis start/end props<commit_after>from datetime import datetime
import json

from django.core.urlresolvers import reverse
from django.http import Http404, HttpResponse
from django.shortcuts import render, get_object_or_404, redirect
from django.template import loader, Context

from django.contrib.auth.decorators import login_required
from django.contrib.sites.models import Site

from symposion.schedule.forms import SlotEditForm
from symposion.schedule.models import Schedule, Day, Slot, Presentation
from symposion.schedule.timetable import TimeTable

def fetch_schedule(slug):
    qs = Schedule.objects.all()

    if slug is None:
        if qs.count() > 1:
            raise Http404()
        schedule = next(iter(qs), None)
        if schedule is None:
            raise Http404()
    else:
        schedule = get_object_or_404(qs, section__slug=slug)

    return schedule

def schedule_conference(request):

    schedules = Schedule.objects.filter(published=True, hidden=False)

    sections = []
    for schedule in schedules:
        days_qs = Day.objects.filter(schedule=schedule)
        days = [TimeTable(day) for day in days_qs]
        sections.append({
            "schedule": schedule,
            "days": days,
        })

    ctx = {
        "sections": sections,
    }
    return render(request, "schedule/schedule_conference.html", ctx)

def schedule_detail(request, slug=None):

    schedule = fetch_schedule(slug)
    if not schedule.published and not request.user.is_staff:
        raise Http404()

    days_qs = Day.objects.filter(schedule=schedule)
    days = [TimeTable(day) for day in days_qs]

    ctx = {
        "schedule": schedule,
        "days": days,
    }
    return render(request, "schedule/schedule_detail.html", ctx)

def schedule_list(request, slug=None):
    schedule = fetch_schedule(slug)

    presentations = Presentation.objects.filter(section=schedule.section)
    presentations = presentations.exclude(cancelled=True)

    ctx = {
        "schedule": schedule,
        "presentations": presentations,
    }
    return render(request, "schedule/schedule_list.html", ctx)

def schedule_list_csv(request, slug=None):
    schedule = fetch_schedule(slug)

    presentations = Presentation.objects.filter(section=schedule.section)
    presentations = presentations.exclude(cancelled=True).order_by("id")

    response = HttpResponse(mimetype="text/csv")
    if slug:
        file_slug = slug
    else:
        file_slug = "presentations"
    response["Content-Disposition"] = 'attachment; filename="%s.csv"' % file_slug

    response.write(loader.get_template("schedule/schedule_list.csv").render(Context({
        "presentations": presentations,

    })))
    return response

@login_required
def schedule_edit(request, slug=None):

    if not request.user.is_staff:
        raise Http404()

    schedule = fetch_schedule(slug)

    days_qs = Day.objects.filter(schedule=schedule)
    days = [TimeTable(day) for day in days_qs]
    ctx = {
        "schedule": schedule,
        "days": days,
    }
    return render(request, "schedule/schedule_edit.html", ctx)

@login_required
def schedule_slot_edit(request, slug, slot_pk):

    if not request.user.is_staff:
        raise Http404()

    slot = get_object_or_404(Slot, day__schedule__section__slug=slug, pk=slot_pk)

    if request.method == "POST":
        form = SlotEditForm(request.POST, slot=slot)
        if form.is_valid():
            save = False
            if "content_override" in form.cleaned_data:
                slot.content_override = form.cleaned_data["content_override"]
                save = True
            if "presentation" in form.cleaned_data:
                presentation = form.cleaned_data["presentation"]
                if presentation is None:
                    slot.unassign()
                else:
                    slot.assign(presentation)
            if save:
                slot.save()
        return redirect("schedule_edit", slug)
    else:
        form = SlotEditForm(slot=slot)
        ctx = {
            "slug": slug,
            "form": form,
            "slot": slot,
        }
        return render(request, "schedule/_slot_edit.html", ctx)

def schedule_presentation_detail(request, pk):

    presentation = get_object_or_404(Presentation, pk=pk)
    if presentation.slot:
        schedule = presentation.slot.day.schedule
    else:
        schedule = None

    ctx = {
        "presentation": presentation,
        "schedule": schedule,
    }
    return render(request, "schedule/presentation_detail.html", ctx)

def schedule_json(request):
    slots = Slot.objects.filter(
        day__schedule__published=True,
        day__schedule__hidden=False
    ).order_by("start")

    protocol = request.META.get('HTTP_X_FORWARDED_PROTO', 'http')
    data = []
    for slot in slots:
        slot_data = {
            "room": ", ".join(room["name"] for room in slot.rooms.values()),
            "rooms": [room["name"] for room in slot.rooms.values()],
            "start": slot.start_datetime.isoformat(),
            "end": slot.end_datetime.isoformat(),
            "duration": slot.length_in_minutes,
            "kind": slot.kind.label,
            "section": slot.day.schedule.section.slug,
        }
        if hasattr(slot.content, "proposal"):
            slot_data.update({
                "name": slot.content.title,
                "authors": [s.name for s in slot.content.speakers()],
                "contact": [
                    s.email for s in slot.content.speakers()
                ] if request.user.is_staff else ["redacted"],
                "abstract": slot.content.abstract.raw,
                "description": slot.content.description.raw,
                "content_href": "%s://%s%s" % (
                    protocol,
                    Site.objects.get_current().domain,
                    reverse("schedule_presentation_detail", args=[slot.content.pk])
                ),
                "cancelled": slot.content.cancelled,
            })
        else:
            slot_data.update({
                "name": slot.content_override.raw if slot.content_override else "Slot",
            })
        data.append(slot_data)

    return HttpResponse(
        json.dumps({'schedule': data}),
        content_type="application/json"
    )
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from twisted.internet import defer

from synapse.api.events.room import (
    RoomMemberEvent, RoomTopicEvent, FeedbackEvent,
#   RoomConfigEvent,
    RoomNameEvent,
    RoomJoinRulesEvent,
    RoomPowerLevelsEvent,
    RoomAddStateLevelEvent,
    RoomSendEventLevelEvent,
    RoomOpsPowerLevelsEvent,
    RoomRedactionEvent,
)

from synapse.util.logutils import log_function

from .directory import DirectoryStore
from .feedback import FeedbackStore
from .presence import PresenceStore
from .profile import ProfileStore
from .registration import RegistrationStore
from .room import RoomStore
from .roommember import RoomMemberStore
from .stream import StreamStore
from .pdu import StatePduStore, PduStore, PdusTable
from .transactions import TransactionStore
from .keys import KeyStore

import json
import logging
import os

logger = logging.getLogger(__name__)

SCHEMAS = [
    "transactions",
    "pdu",
    "users",
    "profiles",
    "presence",
    "im",
    "room_aliases",
    "keys",
    "redactions",
]

# Remember to update this number every time an incompatible change is made to
# database schema files, so the users will be informed on server restarts.
SCHEMA_VERSION = 6

class _RollbackButIsFineException(Exception):
    """ This exception is used to rollback a transaction without implying
    something went wrong.
    """
    pass

class DataStore(RoomMemberStore, RoomStore,
                RegistrationStore, StreamStore, ProfileStore, FeedbackStore,
                PresenceStore, PduStore, StatePduStore, TransactionStore,
                DirectoryStore, KeyStore):

    def __init__(self, hs):
        super(DataStore, self).__init__(hs)
        self.event_factory = hs.get_event_factory()
        self.hs = hs

        self.min_token_deferred = self._get_min_token()
        self.min_token = None

    @defer.inlineCallbacks
    @log_function
    def persist_event(self, event=None, backfilled=False, pdu=None,
                      is_new_state=True):
        stream_ordering = None
        if backfilled:
            if not self.min_token_deferred.called:
                yield self.min_token_deferred
            self.min_token -= 1
            stream_ordering = self.min_token

        try:
            yield self.runInteraction(
                self._persist_pdu_event_txn,
                pdu=pdu,
                event=event,
                backfilled=backfilled,
                stream_ordering=stream_ordering,
                is_new_state=is_new_state,
            )
        except _RollbackButIsFineException:
            pass

    @defer.inlineCallbacks
    def get_event(self, event_id, allow_none=False):
        events_dict = yield self._simple_select_one(
            "events",
            {"event_id": event_id},
            [
                "event_id",
                "type",
                "room_id",
                "content",
                "unrecognized_keys"
            ],
            allow_none=allow_none,
        )

        if not events_dict:
            defer.returnValue(None)

        event = self._parse_event_from_row(events_dict)
        defer.returnValue(event)

    def _persist_pdu_event_txn(self, txn, pdu=None, event=None,
                               backfilled=False, stream_ordering=None,
                               is_new_state=True):
        if pdu is not None:
            self._persist_event_pdu_txn(txn, pdu)
        if event is not None:
            return self._persist_event_txn(
                txn, event, backfilled, stream_ordering,
                is_new_state=is_new_state,
            )

    def _persist_event_pdu_txn(self, txn, pdu):
        cols = dict(pdu.__dict__)
        unrec_keys = dict(pdu.unrecognized_keys)
        del cols["content"]
        del cols["prev_pdus"]
        cols["content_json"] = json.dumps(pdu.content)

        unrec_keys.update({
            k: v for k, v in cols.items()
            if k not in PdusTable.fields
        })

        cols["unrecognized_keys"] = json.dumps(unrec_keys)

        cols["ts"] = cols.pop("origin_server_ts")

        logger.debug("Persisting: %s", repr(cols))

        if pdu.is_state:
            self._persist_state_txn(txn, pdu.prev_pdus, cols)
        else:
            self._persist_pdu_txn(txn, pdu.prev_pdus, cols)

        self._update_min_depth_for_context_txn(txn, pdu.context, pdu.depth)

    @log_function
    def _persist_event_txn(self, txn, event, backfilled, stream_ordering=None,
                           is_new_state=True):
        if event.type == RoomMemberEvent.TYPE:
            self._store_room_member_txn(txn, event)
        elif event.type == FeedbackEvent.TYPE:
            self._store_feedback_txn(txn, event)
        elif event.type == RoomNameEvent.TYPE:
            self._store_room_name_txn(txn, event)
        elif event.type == RoomTopicEvent.TYPE:
            self._store_room_topic_txn(txn, event)
        elif event.type == RoomJoinRulesEvent.TYPE:
            self._store_join_rule(txn, event)
        elif event.type == RoomPowerLevelsEvent.TYPE:
            self._store_power_levels(txn, event)
        elif event.type == RoomAddStateLevelEvent.TYPE:
            self._store_add_state_level(txn, event)
        elif event.type == RoomSendEventLevelEvent.TYPE:
            self._store_send_event_level(txn, event)
        elif event.type == RoomOpsPowerLevelsEvent.TYPE:
            self._store_ops_level(txn, event)
        elif event.type == RoomRedactionEvent.TYPE:
            self._store_redaction(txn, event)

        vals = {
            "topological_ordering": event.depth,
            "event_id": event.event_id,
            "type": event.type,
            "room_id": event.room_id,
            "content": json.dumps(event.content),
            "processed": True,
        }

        if stream_ordering is not None:
            vals["stream_ordering"] = stream_ordering

        if hasattr(event, "outlier"):
            vals["outlier"] = event.outlier
        else:
            vals["outlier"] = False

        unrec = {
            k: v
            for k, v in event.get_full_dict().items()
            if k not in vals.keys() and k not in ["redacted", "redacted_because"]
        }
        vals["unrecognized_keys"] = json.dumps(unrec)

        try:
            self._simple_insert_txn(txn, "events", vals)
        except:
            logger.warn(
                "Failed to persist, probably duplicate: %s",
                event.event_id,
                exc_info=True,
            )
            raise _RollbackButIsFineException("_persist_event")

        is_state = hasattr(event, "state_key") and event.state_key is not None
        if is_new_state and is_state:
            vals = {
                "event_id": event.event_id,
                "room_id": event.room_id,
                "type": event.type,
                "state_key": event.state_key,
            }

            if hasattr(event, "prev_state"):
                vals["prev_state"] = event.prev_state

            self._simple_insert_txn(txn, "state_events", vals)

            self._simple_insert_txn(
                txn,
                "current_state_events",
                {
                    "event_id": event.event_id,
                    "room_id": event.room_id,
                    "type": event.type,
                    "state_key": event.state_key,
                }
            )

    def _store_redaction(self, txn, event):
        txn.execute(
            "INSERT OR IGNORE INTO redactions "
            "(event_id, redacts) VALUES (?,?)",
            (event.event_id, event.redacts)
        )

    @defer.inlineCallbacks
    def get_current_state(self, room_id, event_type=None, state_key=""):
        del_sql = (
            "SELECT event_id FROM redactions WHERE redacts = e.event_id "
            "LIMIT 1"
        )

        sql = (
            "SELECT e.*, (%(redacted)s) AS redacted FROM events as e "
            "INNER JOIN current_state_events as c ON e.event_id = c.event_id "
            "INNER JOIN state_events as s ON e.event_id = s.event_id "
            "WHERE c.room_id = ? "
        ) % {
            "redacted": del_sql,
        }

        if event_type:
            sql += " AND s.type = ? AND s.state_key = ? "
            args = (room_id, event_type, state_key)
        else:
            args = (room_id, )

        results = yield self._execute_and_decode(sql, *args)

        events = yield self._parse_events(results)
        defer.returnValue(events)

    @defer.inlineCallbacks
    def _get_min_token(self):
        row = yield self._execute(
            None,
            "SELECT MIN(stream_ordering) FROM events"
        )

        self.min_token = row[0][0] if row and row[0] and row[0][0] else -1
        self.min_token = min(self.min_token, -1)

        logger.debug("min_token is: %s", self.min_token)

        defer.returnValue(self.min_token)

    def insert_client_ip(self, user, access_token, device_id, ip, user_agent):
        return self._simple_insert(
            "user_ips",
            {
                "user": user.to_string(),
                "access_token": access_token,
                "device_id": device_id,
                "ip": ip,
                "user_agent": user_agent,
                "last_seen": int(self._clock.time_msec()),
            }
        )

    def get_user_ip_and_agents(self, user):
        return self._simple_select_list(
            table="user_ips",
            keyvalues={"user": user.to_string()},
            retcols=[
                "device_id", "access_token", "ip", "user_agent", "last_seen"
            ],
        )

    def snapshot_room(self, room_id, user_id, state_type=None, state_key=None):
        """Snapshot the room for an update by a user
        Args:
            room_id (synapse.types.RoomId): The room to snapshot.
            user_id (synapse.types.UserId): The user to snapshot the room for.
            state_type (str): Optional state type to snapshot.
            state_key (str): Optional state key to snapshot.
        Returns:
            synapse.storage.Snapshot: A snapshot of the state of the room.
        """
        def _snapshot(txn):
            membership_state = self._get_room_member(txn, user_id, room_id)
            prev_pdus = self._get_latest_pdus_in_context(
                txn, room_id
            )
            if state_type is not None and state_key is not None:
                prev_state_pdu = self._get_current_state_pdu(
                    txn, room_id, state_type, state_key
                )
            else:
                prev_state_pdu = None

            return Snapshot(
                store=self,
                room_id=room_id,
                user_id=user_id,
                prev_pdus=prev_pdus,
                membership_state=membership_state,
                state_type=state_type,
                state_key=state_key,
                prev_state_pdu=prev_state_pdu,
            )

        return self.runInteraction(_snapshot)

class Snapshot(object):
    """Snapshot of the state of a room
    Args:
        store (DataStore): The datastore.
        room_id (RoomId): The room of the snapshot.
        user_id (UserId): The user this snapshot is for.
        prev_pdus (list): The list of PDU ids this snapshot is after.
        membership_state (RoomMemberEvent): The current state of the user in
            the room.
        state_type (str, optional): State type captured by the snapshot
        state_key (str, optional): State key captured by the snapshot
        prev_state_pdu (PduEntry, optional): pdu id of
            the previous value of the state type and key in the room.
    """

    def __init__(self, store, room_id, user_id, prev_pdus,
                 membership_state, state_type=None, state_key=None,
                 prev_state_pdu=None):
        self.store = store
        self.room_id = room_id
        self.user_id = user_id
        self.prev_pdus = prev_pdus
        self.membership_state = membership_state
        self.state_type = state_type
        self.state_key = state_key
        self.prev_state_pdu = prev_state_pdu

    def fill_out_prev_events(self, event):
        if hasattr(event, "prev_events"):
            return

        es = [
            "%s@%s" % (p_id, origin) for p_id, origin, _ in self.prev_pdus
        ]

        event.prev_events = [e for e in es if e != event.event_id]

        if self.prev_pdus:
            event.depth = max([int(v) for _, _, v in self.prev_pdus]) + 1
        else:
            event.depth = 0

def schema_path(schema):
    """ Get a filesystem path for the named database schema

    Args:
        schema: Name of the database schema.
    Returns:
        A filesystem path pointing at a ".sql" file.

    """
    dir_path = os.path.dirname(__file__)
    schemaPath = os.path.join(dir_path, "schema", schema + ".sql")
    return schemaPath

def read_schema(schema):
    """ Read the named database schema.

    Args:
        schema: Name of the datbase schema.
    Returns:
        A string containing the database schema.
    """
    with open(schema_path(schema)) as schema_file:
        return schema_file.read()

def prepare_database(db_conn):
    """ Set up all the dbs. Since all the *.sql have IF NOT EXISTS, so we
    don't have to worry about overwriting existing content.
    """
    c = db_conn.cursor()
    c.execute("PRAGMA user_version")
    row = c.fetchone()

    if row and row[0]:
        user_version = row[0]

        if user_version > SCHEMA_VERSION:
            raise ValueError("Cannot use this database as it is too " +
                "new for the server to understand"
            )
        elif user_version < SCHEMA_VERSION:
            logging.info("Upgrading database from version %d",
                user_version
            )

            # Run every version since after the current version.
            for v in range(user_version + 1, SCHEMA_VERSION + 1):
                sql_script = read_schema("delta/v%d" % (v))
                c.executescript(sql_script)

            db_conn.commit()

    else:
        for sql_loc in SCHEMAS:
            sql_script = read_schema(sql_loc)

            c.executescript(sql_script)
        db_conn.commit()
        c.execute("PRAGMA user_version = %d" % SCHEMA_VERSION)

    c.close()

<commit_msg>Wrap preparing the database in a transaction. Otherwise it will take many seconds to complete because sqlite will create a transaction per statement<commit_after># -*- coding: utf-8 -*-
# Copyright 2014 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from twisted.internet import defer

from synapse.api.events.room import (
    RoomMemberEvent, RoomTopicEvent, FeedbackEvent,
#   RoomConfigEvent,
    RoomNameEvent,
    RoomJoinRulesEvent,
    RoomPowerLevelsEvent,
    RoomAddStateLevelEvent,
    RoomSendEventLevelEvent,
    RoomOpsPowerLevelsEvent,
    RoomRedactionEvent,
)

from synapse.util.logutils import log_function

from .directory import DirectoryStore
from .feedback import FeedbackStore
from .presence import PresenceStore
from .profile import ProfileStore
from .registration import RegistrationStore
from .room import RoomStore
from .roommember import RoomMemberStore
from .stream import StreamStore
from .pdu import StatePduStore, PduStore, PdusTable
from .transactions import TransactionStore
from .keys import KeyStore

import json
import logging
import os

logger = logging.getLogger(__name__)

SCHEMAS = [
    "transactions",
    "pdu",
    "users",
    "profiles",
    "presence",
    "im",
    "room_aliases",
    "keys",
    "redactions",
]

# Remember to update this number every time an incompatible change is made to
# database schema files, so the users will be informed on server restarts.
SCHEMA_VERSION = 6

class _RollbackButIsFineException(Exception):
    """ This exception is used to rollback a transaction without implying
    something went wrong.
    """
    pass

class DataStore(RoomMemberStore, RoomStore,
                RegistrationStore, StreamStore, ProfileStore, FeedbackStore,
                PresenceStore, PduStore, StatePduStore, TransactionStore,
                DirectoryStore, KeyStore):

    def __init__(self, hs):
        super(DataStore, self).__init__(hs)
        self.event_factory = hs.get_event_factory()
        self.hs = hs

        self.min_token_deferred = self._get_min_token()
        self.min_token = None

    @defer.inlineCallbacks
    @log_function
    def persist_event(self, event=None, backfilled=False, pdu=None,
                      is_new_state=True):
        stream_ordering = None
        if backfilled:
            if not self.min_token_deferred.called:
                yield self.min_token_deferred
            self.min_token -= 1
            stream_ordering = self.min_token

        try:
            yield self.runInteraction(
                self._persist_pdu_event_txn,
                pdu=pdu,
                event=event,
                backfilled=backfilled,
                stream_ordering=stream_ordering,
                is_new_state=is_new_state,
            )
        except _RollbackButIsFineException:
            pass

    @defer.inlineCallbacks
    def get_event(self, event_id, allow_none=False):
        events_dict = yield self._simple_select_one(
            "events",
            {"event_id": event_id},
            [
                "event_id",
                "type",
                "room_id",
                "content",
                "unrecognized_keys"
            ],
            allow_none=allow_none,
        )

        if not events_dict:
            defer.returnValue(None)

        event = self._parse_event_from_row(events_dict)
        defer.returnValue(event)

    def _persist_pdu_event_txn(self, txn, pdu=None, event=None,
                               backfilled=False, stream_ordering=None,
                               is_new_state=True):
        if pdu is not None:
            self._persist_event_pdu_txn(txn, pdu)
        if event is not None:
            return self._persist_event_txn(
                txn, event, backfilled, stream_ordering,
                is_new_state=is_new_state,
            )

    def _persist_event_pdu_txn(self, txn, pdu):
        cols = dict(pdu.__dict__)
        unrec_keys = dict(pdu.unrecognized_keys)
        del cols["content"]
        del cols["prev_pdus"]
        cols["content_json"] = json.dumps(pdu.content)

        unrec_keys.update({
            k: v for k, v in cols.items()
            if k not in PdusTable.fields
        })

        cols["unrecognized_keys"] = json.dumps(unrec_keys)

        cols["ts"] = cols.pop("origin_server_ts")

        logger.debug("Persisting: %s", repr(cols))

        if pdu.is_state:
            self._persist_state_txn(txn, pdu.prev_pdus, cols)
        else:
            self._persist_pdu_txn(txn, pdu.prev_pdus, cols)

        self._update_min_depth_for_context_txn(txn, pdu.context, pdu.depth)

    @log_function
    def _persist_event_txn(self, txn, event, backfilled, stream_ordering=None,
                           is_new_state=True):
        if event.type == RoomMemberEvent.TYPE:
            self._store_room_member_txn(txn, event)
        elif event.type == FeedbackEvent.TYPE:
            self._store_feedback_txn(txn, event)
        elif event.type == RoomNameEvent.TYPE:
            self._store_room_name_txn(txn, event)
        elif event.type == RoomTopicEvent.TYPE:
            self._store_room_topic_txn(txn, event)
        elif event.type == RoomJoinRulesEvent.TYPE:
            self._store_join_rule(txn, event)
        elif event.type == RoomPowerLevelsEvent.TYPE:
            self._store_power_levels(txn, event)
        elif event.type == RoomAddStateLevelEvent.TYPE:
            self._store_add_state_level(txn, event)
        elif event.type == RoomSendEventLevelEvent.TYPE:
            self._store_send_event_level(txn, event)
        elif event.type == RoomOpsPowerLevelsEvent.TYPE:
            self._store_ops_level(txn, event)
        elif event.type == RoomRedactionEvent.TYPE:
            self._store_redaction(txn, event)

        vals = {
            "topological_ordering": event.depth,
            "event_id": event.event_id,
            "type": event.type,
            "room_id": event.room_id,
            "content": json.dumps(event.content),
            "processed": True,
        }

        if stream_ordering is not None:
            vals["stream_ordering"] = stream_ordering

        if hasattr(event, "outlier"):
            vals["outlier"] = event.outlier
        else:
            vals["outlier"] = False

        unrec = {
            k: v
            for k, v in event.get_full_dict().items()
            if k not in vals.keys() and k not in ["redacted", "redacted_because"]
        }
        vals["unrecognized_keys"] = json.dumps(unrec)

        try:
            self._simple_insert_txn(txn, "events", vals)
        except:
            logger.warn(
                "Failed to persist, probably duplicate: %s",
                event.event_id,
                exc_info=True,
            )
            raise _RollbackButIsFineException("_persist_event")

        is_state = hasattr(event, "state_key") and event.state_key is not None
        if is_new_state and is_state:
            vals = {
                "event_id": event.event_id,
                "room_id": event.room_id,
                "type": event.type,
                "state_key": event.state_key,
            }

            if hasattr(event, "prev_state"):
                vals["prev_state"] = event.prev_state

            self._simple_insert_txn(txn, "state_events", vals)

            self._simple_insert_txn(
                txn,
                "current_state_events",
                {
                    "event_id": event.event_id,
                    "room_id": event.room_id,
                    "type": event.type,
                    "state_key": event.state_key,
                }
            )

    def _store_redaction(self, txn, event):
        txn.execute(
            "INSERT OR IGNORE INTO redactions "
            "(event_id, redacts) VALUES (?,?)",
            (event.event_id, event.redacts)
        )

    @defer.inlineCallbacks
    def get_current_state(self, room_id, event_type=None, state_key=""):
        del_sql = (
            "SELECT event_id FROM redactions WHERE redacts = e.event_id "
            "LIMIT 1"
        )

        sql = (
            "SELECT e.*, (%(redacted)s) AS redacted FROM events as e "
            "INNER JOIN current_state_events as c ON e.event_id = c.event_id "
            "INNER JOIN state_events as s ON e.event_id = s.event_id "
            "WHERE c.room_id = ? "
        ) % {
            "redacted": del_sql,
        }

        if event_type:
            sql += " AND s.type = ? AND s.state_key = ? "
            args = (room_id, event_type, state_key)
        else:
            args = (room_id, )

        results = yield self._execute_and_decode(sql, *args)

        events = yield self._parse_events(results)
        defer.returnValue(events)

    @defer.inlineCallbacks
    def _get_min_token(self):
        row = yield self._execute(
            None,
            "SELECT MIN(stream_ordering) FROM events"
        )

        self.min_token = row[0][0] if row and row[0] and row[0][0] else -1
        self.min_token = min(self.min_token, -1)

        logger.debug("min_token is: %s", self.min_token)

        defer.returnValue(self.min_token)

    def insert_client_ip(self, user, access_token, device_id, ip, user_agent):
        return self._simple_insert(
            "user_ips",
            {
                "user": user.to_string(),
                "access_token": access_token,
                "device_id": device_id,
                "ip": ip,
                "user_agent": user_agent,
                "last_seen": int(self._clock.time_msec()),
            }
        )

    def get_user_ip_and_agents(self, user):
        return self._simple_select_list(
            table="user_ips",
            keyvalues={"user": user.to_string()},
            retcols=[
                "device_id", "access_token", "ip", "user_agent", "last_seen"
            ],
        )

    def snapshot_room(self, room_id, user_id, state_type=None, state_key=None):
        """Snapshot the room for an update by a user
        Args:
            room_id (synapse.types.RoomId): The room to snapshot.
            user_id (synapse.types.UserId): The user to snapshot the room for.
            state_type (str): Optional state type to snapshot.
            state_key (str): Optional state key to snapshot.
        Returns:
            synapse.storage.Snapshot: A snapshot of the state of the room.
        """
        def _snapshot(txn):
            membership_state = self._get_room_member(txn, user_id, room_id)
            prev_pdus = self._get_latest_pdus_in_context(
                txn, room_id
            )
            if state_type is not None and state_key is not None:
                prev_state_pdu = self._get_current_state_pdu(
                    txn, room_id, state_type, state_key
                )
            else:
                prev_state_pdu = None

            return Snapshot(
                store=self,
                room_id=room_id,
                user_id=user_id,
                prev_pdus=prev_pdus,
                membership_state=membership_state,
                state_type=state_type,
                state_key=state_key,
                prev_state_pdu=prev_state_pdu,
            )

        return self.runInteraction(_snapshot)

class Snapshot(object):
    """Snapshot of the state of a room
    Args:
        store (DataStore): The datastore.
        room_id (RoomId): The room of the snapshot.
        user_id (UserId): The user this snapshot is for.
        prev_pdus (list): The list of PDU ids this snapshot is after.
        membership_state (RoomMemberEvent): The current state of the user in
            the room.
        state_type (str, optional): State type captured by the snapshot
        state_key (str, optional): State key captured by the snapshot
        prev_state_pdu (PduEntry, optional): pdu id of
            the previous value of the state type and key in the room.
    """

    def __init__(self, store, room_id, user_id, prev_pdus,
                 membership_state, state_type=None, state_key=None,
                 prev_state_pdu=None):
        self.store = store
        self.room_id = room_id
        self.user_id = user_id
        self.prev_pdus = prev_pdus
        self.membership_state = membership_state
        self.state_type = state_type
        self.state_key = state_key
        self.prev_state_pdu = prev_state_pdu

    def fill_out_prev_events(self, event):
        if hasattr(event, "prev_events"):
            return

        es = [
            "%s@%s" % (p_id, origin) for p_id, origin, _ in self.prev_pdus
        ]

        event.prev_events = [e for e in es if e != event.event_id]

        if self.prev_pdus:
            event.depth = max([int(v) for _, _, v in self.prev_pdus]) + 1
        else:
            event.depth = 0

def schema_path(schema):
    """ Get a filesystem path for the named database schema

    Args:
        schema: Name of the database schema.
    Returns:
        A filesystem path pointing at a ".sql" file.

    """
    dir_path = os.path.dirname(__file__)
    schemaPath = os.path.join(dir_path, "schema", schema + ".sql")
    return schemaPath

def read_schema(schema):
    """ Read the named database schema.

    Args:
        schema: Name of the datbase schema.
    Returns:
        A string containing the database schema.
    """
    with open(schema_path(schema)) as schema_file:
        return schema_file.read()

def prepare_database(db_conn):
    """ Set up all the dbs. Since all the *.sql have IF NOT EXISTS, so we
    don't have to worry about overwriting existing content.
    """
    c = db_conn.cursor()
    c.execute("PRAGMA user_version")
    row = c.fetchone()

    if row and row[0]:
        user_version = row[0]

        if user_version > SCHEMA_VERSION:
            raise ValueError("Cannot use this database as it is too " +
                "new for the server to understand"
            )
        elif user_version < SCHEMA_VERSION:
            logging.info("Upgrading database from version %d",
                user_version
            )

            # Run every version since after the current version.
            for v in range(user_version + 1, SCHEMA_VERSION + 1):
                sql_script = read_schema("delta/v%d" % (v))
                c.executescript(sql_script)

            db_conn.commit()

    else:
        sql_script = "BEGIN TRANSACTION;"
        for sql_loc in SCHEMAS:
            sql_script += read_schema(sql_loc)
        sql_script += "COMMIT TRANSACTION;"
        c.executescript(sql_script)
        db_conn.commit()
        c.execute("PRAGMA user_version = %d" % SCHEMA_VERSION)

    c.close()

<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from twisted.internet import defer
from .appservice import (
    ApplicationServiceStore, ApplicationServiceTransactionStore
)
from ._base import Cache
from .directory import DirectoryStore
from .events import EventsStore
from .presence import PresenceStore
from .profile import ProfileStore
from .registration import RegistrationStore
from .room import RoomStore
from .roommember import RoomMemberStore
from .stream import StreamStore
from .transactions import TransactionStore
from .keys import KeyStore
from .event_federation import EventFederationStore
from .pusher import PusherStore
from .push_rule import PushRuleStore
from .media_repository import MediaRepositoryStore
from .rejections import RejectionsStore
from .event_push_actions import EventPushActionsStore

from .state import StateStore
from .signatures import SignatureStore
from .filtering import FilteringStore
from .end_to_end_keys import EndToEndKeyStore

from .receipts import ReceiptsStore
from .search import SearchStore
from .tags import TagsStore
from .account_data import AccountDataStore

from util.id_generators import IdGenerator, StreamIdGenerator

from synapse.util.caches.stream_change_cache import StreamChangeCache

import logging

logger = logging.getLogger(__name__)

# Number of msec of granularity to store the user IP 'last seen' time. Smaller
# times give more inserts into the database even for readonly API hits
# 120 seconds == 2 minutes
LAST_SEEN_GRANULARITY = 120*1000

class DataStore(RoomMemberStore, RoomStore,
                RegistrationStore, StreamStore, ProfileStore,
                PresenceStore, TransactionStore,
                DirectoryStore, KeyStore, StateStore, SignatureStore,
                ApplicationServiceStore,
                EventFederationStore,
                MediaRepositoryStore,
                RejectionsStore,
                FilteringStore,
                PusherStore,
                PushRuleStore,
                ApplicationServiceTransactionStore,
                EventsStore,
                ReceiptsStore,
                EndToEndKeyStore,
                SearchStore,
                TagsStore,
                AccountDataStore,
                EventPushActionsStore
                ):

    def __init__(self, db_conn, hs):
        self.hs = hs

        cur = db_conn.cursor()
        try:
            cur.execute("SELECT MIN(stream_ordering) FROM events",)
            rows = cur.fetchall()
            self.min_stream_token = rows[0][0] if rows and rows[0] and rows[0][0] else -1
            self.min_stream_token = min(self.min_stream_token, -1)
        finally:
            cur.close()

        self.client_ip_last_seen = Cache(
            name="client_ip_last_seen",
            keylen=4,
        )

        self._stream_id_gen = StreamIdGenerator(
            db_conn, "events", "stream_ordering"
        )
        self._receipts_id_gen = StreamIdGenerator(
            db_conn, "receipts_linearized", "stream_id"
        )
        self._account_data_id_gen = StreamIdGenerator(
            db_conn, "account_data_max_stream_id", "stream_id"
        )

        self._transaction_id_gen = IdGenerator("sent_transactions", "id", self)
        self._state_groups_id_gen = IdGenerator("state_groups", "id", self)
        self._access_tokens_id_gen = IdGenerator("access_tokens", "id", self)
        self._refresh_tokens_id_gen = IdGenerator("refresh_tokens", "id", self)
        self._pushers_id_gen = IdGenerator("pushers", "id", self)
        self._push_rule_id_gen = IdGenerator("push_rules", "id", self)
        self._push_rules_enable_id_gen = IdGenerator("push_rules_enable", "id", self)

        events_max = self._stream_id_gen.get_max_token(None)
        event_cache_prefill = self._get_cache_dict(
            db_conn, "events",
            entity_column="room_id",
            stream_column="stream_ordering",
            max_value=events_max,
        )
        self._events_stream_cache = StreamChangeCache(
            "EventsRoomStreamChangeCache", events_max,
            prefilled_cache=event_cache_prefill,
        )

        account_max = self._account_data_id_gen.get_max_token(None)
        account_cache_prefill = self._get_cache_dict(
            db_conn, "account_data",
            entity_column="user_id",
            stream_column="stream_id",
            max_value=account_max,
        )
        self._account_data_stream_cache = StreamChangeCache(
            "AccountDataAndTagsChangeCache", account_max,
            prefilled_cache=account_cache_prefill,
        )

        super(DataStore, self).__init__(hs)

    def _get_cache_dict(self, db_conn, table, entity_column, stream_column, max_value):
        sql = (
            "SELECT %(entity)s, MAX(%(stream)s) FROM %(table)s"
            " WHERE %(stream)s > max(? - 100000, 0)"
            " GROUP BY %(entity)s"
            " ORDER BY MAX(%(stream)s) DESC"
            " LIMIT 10000"
        ) % {
            "table": table,
            "entity": entity_column,
            "stream": stream_column,
        }

        txn = db_conn.cursor()
        txn.execute(sql, (int(max_value),))
        rows = txn.fetchall()

        return {
            row[0]: row[1]
            for row in rows
        }

    @defer.inlineCallbacks
    def insert_client_ip(self, user, access_token, ip, user_agent):
        now = int(self._clock.time_msec())
        key = (user.to_string(), access_token, ip)

        try:
            last_seen = self.client_ip_last_seen.get(key)
        except KeyError:
            last_seen = None

        # Rate-limited inserts
        if last_seen is not None and (now - last_seen) < LAST_SEEN_GRANULARITY:
            defer.returnValue(None)

        self.client_ip_last_seen.prefill(key, now)

        # It's safe not to lock here: a) no unique constraint,
        # b) LAST_SEEN_GRANULARITY makes concurrent updates incredibly unlikely
        yield self._simple_upsert(
            "user_ips",
            keyvalues={
                "user_id": user.to_string(),
                "access_token": access_token,
                "ip": ip,
                "user_agent": user_agent,
            },
            values={
                "last_seen": now,
            },
            desc="insert_client_ip",
            lock=False,
        )

    @defer.inlineCallbacks
    def count_daily_users(self):
        """
        Counts the number of users who used this homeserver in the last 24 hours.
        """
        def _count_users(txn):
            txn.execute(
                "SELECT COUNT(DISTINCT user_id) AS users"
                " FROM user_ips"
                " WHERE last_seen > ?",
                # This is close enough to a day for our purposes.
                (int(self._clock.time_msec()) - (1000 * 60 * 60 * 24),)
            )
            rows = self.cursor_to_dict(txn)
            if rows:
                return rows[0]["users"]
            return 0

        ret = yield self.runInteraction("count_users", _count_users)
        defer.returnValue(ret)

    def get_user_ip_and_agents(self, user):
        return self._simple_select_list(
            table="user_ips",
            keyvalues={"user_id": user.to_string()},
            retcols=[
                "access_token", "ip", "user_agent", "last_seen"
            ],
            desc="get_user_ip_and_agents",
        )

def are_all_users_on_domain(txn, database_engine, domain):
    sql = database_engine.convert_param_style(
        "SELECT COUNT(*) FROM users WHERE name NOT LIKE ?"
    )
    pat = "%:" + domain
    txn.execute(sql, (pat,))
    num_not_matching = txn.fetchall()[0][0]
    if num_not_matching == 0:
        return True
    return False
<commit_msg>Convert param style<commit_after># -*- coding: utf-8 -*-
# Copyright 2014-2016 OpenMarket Ltd
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from twisted.internet import defer
from .appservice import (
    ApplicationServiceStore, ApplicationServiceTransactionStore
)
from ._base import Cache
from .directory import DirectoryStore
from .events import EventsStore
from .presence import PresenceStore
from .profile import ProfileStore
from .registration import RegistrationStore
from .room import RoomStore
from .roommember import RoomMemberStore
from .stream import StreamStore
from .transactions import TransactionStore
from .keys import KeyStore
from .event_federation import EventFederationStore
from .pusher import PusherStore
from .push_rule import PushRuleStore
from .media_repository import MediaRepositoryStore
from .rejections import RejectionsStore
from .event_push_actions import EventPushActionsStore

from .state import StateStore
from .signatures import SignatureStore
from .filtering import FilteringStore
from .end_to_end_keys import EndToEndKeyStore

from .receipts import ReceiptsStore
from .search import SearchStore
from .tags import TagsStore
from .account_data import AccountDataStore

from util.id_generators import IdGenerator, StreamIdGenerator

from synapse.util.caches.stream_change_cache import StreamChangeCache

import logging

logger = logging.getLogger(__name__)

# Number of msec of granularity to store the user IP 'last seen' time. Smaller
# times give more inserts into the database even for readonly API hits
# 120 seconds == 2 minutes
LAST_SEEN_GRANULARITY = 120*1000

class DataStore(RoomMemberStore, RoomStore,
                RegistrationStore, StreamStore, ProfileStore,
                PresenceStore, TransactionStore,
                DirectoryStore, KeyStore, StateStore, SignatureStore,
                ApplicationServiceStore,
                EventFederationStore,
                MediaRepositoryStore,
                RejectionsStore,
                FilteringStore,
                PusherStore,
                PushRuleStore,
                ApplicationServiceTransactionStore,
                EventsStore,
                ReceiptsStore,
                EndToEndKeyStore,
                SearchStore,
                TagsStore,
                AccountDataStore,
                EventPushActionsStore
                ):

    def __init__(self, db_conn, hs):
        self.hs = hs
        self.database_engine = hs.database_engine

        cur = db_conn.cursor()
        try:
            cur.execute("SELECT MIN(stream_ordering) FROM events",)
            rows = cur.fetchall()
            self.min_stream_token = rows[0][0] if rows and rows[0] and rows[0][0] else -1
            self.min_stream_token = min(self.min_stream_token, -1)
        finally:
            cur.close()

        self.client_ip_last_seen = Cache(
            name="client_ip_last_seen",
            keylen=4,
        )

        self._stream_id_gen = StreamIdGenerator(
            db_conn, "events", "stream_ordering"
        )
        self._receipts_id_gen = StreamIdGenerator(
            db_conn, "receipts_linearized", "stream_id"
        )
        self._account_data_id_gen = StreamIdGenerator(
            db_conn, "account_data_max_stream_id", "stream_id"
        )

        self._transaction_id_gen = IdGenerator("sent_transactions", "id", self)
        self._state_groups_id_gen = IdGenerator("state_groups", "id", self)
        self._access_tokens_id_gen = IdGenerator("access_tokens", "id", self)
        self._refresh_tokens_id_gen = IdGenerator("refresh_tokens", "id", self)
        self._pushers_id_gen = IdGenerator("pushers", "id", self)
        self._push_rule_id_gen = IdGenerator("push_rules", "id", self)
        self._push_rules_enable_id_gen = IdGenerator("push_rules_enable", "id", self)

        events_max = self._stream_id_gen.get_max_token(None)
        event_cache_prefill = self._get_cache_dict(
            db_conn, "events",
            entity_column="room_id",
            stream_column="stream_ordering",
            max_value=events_max,
        )
        self._events_stream_cache = StreamChangeCache(
            "EventsRoomStreamChangeCache", events_max,
            prefilled_cache=event_cache_prefill,
        )

        account_max = self._account_data_id_gen.get_max_token(None)
        account_cache_prefill = self._get_cache_dict(
            db_conn, "account_data",
            entity_column="user_id",
            stream_column="stream_id",
            max_value=account_max,
        )
        self._account_data_stream_cache = StreamChangeCache(
            "AccountDataAndTagsChangeCache", account_max,
            prefilled_cache=account_cache_prefill,
        )

        super(DataStore, self).__init__(hs)

    def _get_cache_dict(self, db_conn, table, entity_column, stream_column, max_value):
        sql = (
            "SELECT %(entity)s, MAX(%(stream)s) FROM %(table)s"
            " WHERE %(stream)s > max(? - 100000, 0)"
            " GROUP BY %(entity)s"
            " ORDER BY MAX(%(stream)s) DESC"
            " LIMIT 10000"
        ) % {
            "table": table,
            "entity": entity_column,
            "stream": stream_column,
        }

        sql = self.database_engine.convert_param_style(sql)

        txn = db_conn.cursor()
        txn.execute(sql, (int(max_value),))
        rows = txn.fetchall()

        return {
            row[0]: row[1]
            for row in rows
        }

    @defer.inlineCallbacks
    def insert_client_ip(self, user, access_token, ip, user_agent):
        now = int(self._clock.time_msec())
        key = (user.to_string(), access_token, ip)

        try:
            last_seen = self.client_ip_last_seen.get(key)
        except KeyError:
            last_seen = None

        # Rate-limited inserts
        if last_seen is not None and (now - last_seen) < LAST_SEEN_GRANULARITY:
            defer.returnValue(None)

        self.client_ip_last_seen.prefill(key, now)

        # It's safe not to lock here: a) no unique constraint,
        # b) LAST_SEEN_GRANULARITY makes concurrent updates incredibly unlikely
        yield self._simple_upsert(
            "user_ips",
            keyvalues={
                "user_id": user.to_string(),
                "access_token": access_token,
                "ip": ip,
                "user_agent": user_agent,
            },
            values={
                "last_seen": now,
            },
            desc="insert_client_ip",
            lock=False,
        )

    @defer.inlineCallbacks
    def count_daily_users(self):
        """
        Counts the number of users who used this homeserver in the last 24 hours.
        """
        def _count_users(txn):
            txn.execute(
                "SELECT COUNT(DISTINCT user_id) AS users"
                " FROM user_ips"
                " WHERE last_seen > ?",
                # This is close enough to a day for our purposes.
                (int(self._clock.time_msec()) - (1000 * 60 * 60 * 24),)
            )
            rows = self.cursor_to_dict(txn)
            if rows:
                return rows[0]["users"]
            return 0

        ret = yield self.runInteraction("count_users", _count_users)
        defer.returnValue(ret)

    def get_user_ip_and_agents(self, user):
        return self._simple_select_list(
            table="user_ips",
            keyvalues={"user_id": user.to_string()},
            retcols=[
                "access_token", "ip", "user_agent", "last_seen"
            ],
            desc="get_user_ip_and_agents",
        )

def are_all_users_on_domain(txn, database_engine, domain):
    sql = database_engine.convert_param_style(
        "SELECT COUNT(*) FROM users WHERE name NOT LIKE ?"
    )
    pat = "%:" + domain
    txn.execute(sql, (pat,))
    num_not_matching = txn.fetchall()[0][0]
    if num_not_matching == 0:
        return True
    return False
<|endoftext|>