diff options
| author | Daniel Schadt <kingdread@gmx.de> | 2022-06-29 23:16:28 +0200 | 
|---|---|---|
| committer | Daniel Schadt <kingdread@gmx.de> | 2022-06-29 23:16:28 +0200 | 
| commit | 9d6e5933ef00c2b48ceebe097d0dc683827f137e (patch) | |
| tree | 5fbf929e7145a02dc77299f6a44d02623b3d7cc4 | |
| parent | 09ae6bb5e62bb994546682ffc5726067ca08e77a (diff) | |
| download | fietsboek-9d6e5933ef00c2b48ceebe097d0dc683827f137e.tar.gz fietsboek-9d6e5933ef00c2b48ceebe097d0dc683827f137e.tar.bz2 fietsboek-9d6e5933ef00c2b48ceebe097d0dc683827f137e.zip | |
implement different track visibilities
| -rw-r--r-- | fietsboek/models/track.py | 23 | ||||
| -rw-r--r-- | fietsboek/models/user.py | 47 | ||||
| -rw-r--r-- | fietsboek/templates/edit.jinja2 | 2 | ||||
| -rw-r--r-- | fietsboek/templates/edit_form.jinja2 | 13 | ||||
| -rw-r--r-- | fietsboek/templates/finish_upload.jinja2 | 2 | ||||
| -rw-r--r-- | fietsboek/views/detail.py | 5 | ||||
| -rw-r--r-- | fietsboek/views/edit.py | 2 | ||||
| -rw-r--r-- | fietsboek/views/upload.py | 3 | 
8 files changed, 93 insertions, 4 deletions
| diff --git a/fietsboek/models/track.py b/fietsboek/models/track.py index d264e15..6f500b9 100644 --- a/fietsboek/models/track.py +++ b/fietsboek/models/track.py @@ -41,7 +41,7 @@ class TagBag(TypeDecorator):  class Visibility(enum.Enum):      PRIVATE = enum.auto() -    LINK_ONLY = enum.auto() +    FRIENDS = enum.auto()      PUBLIC = enum.auto() @@ -71,6 +71,7 @@ class Track(Base):      gpx = Column(LargeBinary)      visibility = Column(Enum(Visibility))      tags = Column(TagBag) +    link_secret = Column(Text)      owner = relationship('User', back_populates='tracks')      cache = relationship('TrackCache', back_populates='track', uselist=False) @@ -91,6 +92,26 @@ class Track(Base):      def gpx_data(self, value):          self.gpx = gzip.compress(value) +    def is_visible_to(self, user): +        """Checks whether the track is visible to the given user. + +        :param request: The user. +        :type request: fietsboek.models.User +        :return: Whether the track is visible to the current user. +        :rtype: bool +        """ +        # Public tracks are always visible +        if self.visibility == Visibility.PUBLIC: +            return True +        # Tracks are always visible to the owner and the tagged people +        if user == self.owner or user in self.tagged_people: +            return True +        # Alternatively, if the track is set to friends visibility and the +        # logged in user is a friend. +        if self.visibility == Visibility.FRIENDS: +            return request.identity in self.owner.get_friends() +        return False +      def ensure_cache(self):          if self.cache is not None:              return diff --git a/fietsboek/models/user.py b/fietsboek/models/user.py index 9bfcdc4..c9ec5b6 100644 --- a/fietsboek/models/user.py +++ b/fietsboek/models/user.py @@ -5,8 +5,12 @@ from sqlalchemy import (      Text,      LargeBinary,      Boolean, +    Table, +    ForeignKey,  )  from sqlalchemy.orm import relationship +from sqlalchemy.orm.session import object_session +from sqlalchemy import select, union, delete  from .meta import Base @@ -35,6 +39,14 @@ SCRYPT_PARAMETERS = {  } +friends_assoc = Table( +    "friends_assoc", +    Base.metadata, +    Column("user_1_id", ForeignKey("users.id"), primary_key=True), +    Column("user_2_id", ForeignKey("users.id"), primary_key=True), +) + +  class User(Base):      __tablename__ = 'users'      id = Column(Integer, primary_key=True) @@ -82,6 +94,41 @@ class User(Base):          yield from self.tracks          yield from self.tagged_tracks +    def get_friends(self): +        """Returns all friends of the user. + +        This is not a simple SQLAlchemy property because the friendship +        relation  is complex. + +        :return: All friends of this user. +        :rtype: list[User] +        """ +        q1 = select(User).filter(friends_assoc.c.user_1_id == self.id, friends_assoc.c.user_2_id == User.id) +        q2 = select(User).filter(friends_assoc.c.user_2_id == self.id, friends_assoc.c.user_1_id == User.id) +        query = select(User).from_statement(union(q1, q2)) +        yield from object_session(self).execute(query).scalars() + +    def remove_friend(self, friend): +        """Remove the friend relationship between two users. + +        :param friend: The befriended user. +        :type friend: User +        """ +        session = object_session(self) +        session.execute(delete(friends_assoc).filter_by(user_1_id=self.id, user_2_id=friend.id)) +        session.execute(delete(friends_assoc).filter_by(user_1_id=friend.id, user_2_id=self.id)) + +    def add_friend(self, friend): +        """Add the given user as a new friend. + +        :param friend: The user to befriend. +        :type friend: User +        """ +        if self.id > friend.id: +            return friend.add_friend(self) +        stmt = friends_assoc.insert().values(user_1_id=self.id, user_2_id=friend.id) +        object_session(self).execute(stmt) +  Index('idx_users_name', User.name, unique=True)  Index('idx_users_email', User.email, unique=True) diff --git a/fietsboek/templates/edit.jinja2 b/fietsboek/templates/edit.jinja2 index a1985e6..9d5e484 100644 --- a/fietsboek/templates/edit.jinja2 +++ b/fietsboek/templates/edit.jinja2 @@ -9,7 +9,7 @@      <noscript><p>{{ _("page.noscript") }}<p></noscript>    </div>    <form method="POST"> -    {{ edit_form.edit_track(track.title, track.date, track.description, track.tags, badges) }} +    {{ edit_form.edit_track(track.title, track.date, track.visibility, track.description, track.tags, badges) }}      <button type="submit" class="btn btn-primary">{{ _("page.edit.form.submit") }}</button>      <a href="{{ request.route_url('details', id=track.id) }}" class="btn btn-secondary">{{ _("page.edit.form.cancel") }}</a>    </form> diff --git a/fietsboek/templates/edit_form.jinja2 b/fietsboek/templates/edit_form.jinja2 index e69fe2b..7559fe7 100644 --- a/fietsboek/templates/edit_form.jinja2 +++ b/fietsboek/templates/edit_form.jinja2 @@ -1,4 +1,4 @@ -{% macro edit_track(title, date, description, tags, badges) %} +{% macro edit_track(title, date, visibility, description, tags, badges) %}  <div class="mb-3">    <label for="formTitle" class="form-label">{{ _("page.track.form.title") }}</label>    <input class="form-control" type="text" id="formTitle" name="title" value="{{ title | default("", true) }}"> @@ -8,6 +8,17 @@    <input class="form-control" type="datetime-local" id="formDate" name="date" value="{{ date.strftime('%Y-%m-%dT%H:%M') }}">  </div>  <div class="mb-3"> +  <label for="formVisibility" class="form-label">{{ _("page.track.form.visibility") }}</label> +  <select class="form-select" id="formVisibility" name="visibility"> +      <option value="PRIVATE"{% if visibility.name == "PRIVATE" %} selected{% endif %}>{{ _("page.track.form.visibility.private") }}</option> +      <option value="FRIENDS"{% if visibility.name == "FRIENDS" %} selected{% endif %}>{{ _("page.track.form.visibility.friends") }}</option> +      <option value="PUBLIC"{% if visibility.name == "PUBLIC" %} selected{% endif %}>{{ _("page.track.form.visibility.public") }}</option> +  </select> +  <p class="text-secondary"> +    {{ _("page.track.form.visibility.info") }} +  </p> +</div> +<div class="mb-3">    <div>{{ _("page.track.form.tags") }}</div>    <div id="formTags">      {% for tag in tags %} diff --git a/fietsboek/templates/finish_upload.jinja2 b/fietsboek/templates/finish_upload.jinja2 index 02906a2..dcbf655 100644 --- a/fietsboek/templates/finish_upload.jinja2 +++ b/fietsboek/templates/finish_upload.jinja2 @@ -9,7 +9,7 @@      <noscript><p>{{ _("page.noscript") }}<p></noscript>    </div>    <form method="POST"> -    {{ edit_form.edit_track(upload_title, upload_date, upload_description, upload_tags, badges) }} +    {{ edit_form.edit_track(upload_title, upload_date, upload_visibility, upload_description, upload_tags, badges) }}      <button type="submit" class="btn btn-primary">{{ _("page.upload.form.submit") }}</button>      <a href="{{ request.route_url('cancel-upload', id=preview_id) }}" class="btn btn-danger">{{ _("page.upload.form.cancel") }}</a>    </form> diff --git a/fietsboek/views/detail.py b/fietsboek/views/detail.py index eadc76f..f198593 100644 --- a/fietsboek/views/detail.py +++ b/fietsboek/views/detail.py @@ -1,5 +1,6 @@  from pyramid.view import view_config  from pyramid.response import Response +from pyramid.httpexceptions import HTTPForbidden  from sqlalchemy import select @@ -9,6 +10,8 @@ from .. import models, util  def details(request):      query = select(models.Track).filter_by(id=request.matchdict["id"])      track = request.dbsession.execute(query).scalar_one() +    if not track.is_visible_to(request.identity): +        return HTTPForbidden()      description = util.safe_markdown(track.description)      show_edit_link = (track.owner == request.identity)      return { @@ -22,6 +25,8 @@ def details(request):  def gpx(request):      query = select(models.Track).filter_by(id=request.matchdict["id"])      track = request.dbsession.execute(query).scalar_one() +    if not track.is_visible_to(request.identity): +        return HTTPForbidden()      return Response(track.gpx_data, content_type="application/gpx+xml") diff --git a/fietsboek/views/edit.py b/fietsboek/views/edit.py index 3bd3663..9860856 100644 --- a/fietsboek/views/edit.py +++ b/fietsboek/views/edit.py @@ -6,6 +6,7 @@ from sqlalchemy import select  import datetime  from .. import models +from ..models.track import Visibility  @view_config(route_name='edit', renderer='fietsboek:templates/edit.jinja2', permission='edit', request_method='GET') @@ -36,6 +37,7 @@ def do_edit(request):      ]      track.title = request.params["title"] +    track.visibility = Visibility[request.params["visibility"]]      track.description = request.params["description"]      track.tags = set(request.params["tags"].split(" "))      track.date = datetime.datetime.fromisoformat(request.params["date"]) diff --git a/fietsboek/views/upload.py b/fietsboek/views/upload.py index 1494dbc..c08a111 100644 --- a/fietsboek/views/upload.py +++ b/fietsboek/views/upload.py @@ -9,6 +9,7 @@ import datetime  import gpxpy  from .. import models +from ..models.track import Visibility  @view_config(route_name='upload', renderer='fietsboek:templates/upload.jinja2', request_method='GET', permission='upload') @@ -68,6 +69,7 @@ def finish_upload(request):          'preview_id': upload.id,          'upload_title': gpx.name,          'upload_date': gpx.time, +        'upload_visibility': Visibility.PRIVATE,          'upload_description': gpx.description,          'upload_tags': set(),          'badges': badges, @@ -91,6 +93,7 @@ def do_finish_upload(request):          owner=request.identity,          title=request.params["title"],          date=datetime.datetime.fromisoformat(request.params["date"]), +        visibility = Visibility[request.params["visibility"]],          description=request.params["description"],          tags=set(request.params["tags"].split(" ")),          badges=active_badges, | 
