Compare commits
	
		
			22 Commits
		
	
	
		
			drone-ci
			...
			friend-lis
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 2738c5d84c | |||
| 3308be40ee | |||
| 9b01431641 | |||
| 2b1378310a | |||
| 5639c3f578 | |||
| 61f10f951c | |||
| 496b5b6c04 | |||
| dc0b2954c1 | |||
| 36c2f0fd77 | |||
| 27c78ff36f | |||
| 37e08fed22 | |||
| a0fba3f2af | |||
| 48a19a2296 | |||
| 5d886a7853 | |||
| 5550e5ecf3 | |||
| 0a3cfafef3 | |||
| 8e3bcd8ede | |||
| 48ffb0d472 | |||
| c3348d3212 | |||
| 1a69928241 | |||
| 7b935afdad | |||
| 303dd3d082 | 
							
								
								
									
										17
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								.drone.yml
									
									
									
									
									
								
							| @@ -1,17 +0,0 @@ | |||||||
| pipeline: |  | ||||||
|   build: |  | ||||||
|     image: python:3.6.6-alpine3.6 |  | ||||||
|     commands: |  | ||||||
|       - apk update |  | ||||||
|       - apk add gcc libffi-dev linux-headers libc-dev |  | ||||||
|       - pip3.6 install -U pip setuptools pipenv |  | ||||||
|       - pipenv install |  | ||||||
|   lint: |  | ||||||
|     image: python:3.6.6-alpine3.6 |  | ||||||
|     commands: |  | ||||||
|       - apk update |  | ||||||
|       - apk add gcc libffi-dev linux-headers libc-dev |  | ||||||
|       - pip3.6 install -U pip setuptools pipenv |  | ||||||
|       - pipenv install |  | ||||||
|       - pipenv install --dev |  | ||||||
|       - pipenv run pylint calsocial |  | ||||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,5 +1,5 @@ | |||||||
| __pycache__/ | __pycache__/ | ||||||
| /calsocial/local.db | /calsocial/local.db | ||||||
| /messages.pot | /messages.pot | ||||||
| /app/translations/*/LC_MESSAGES/*.mo | /calsocial/translations/*/LC_MESSAGES/*.mo | ||||||
| /.pytest_cache/ | /.pytest_cache/ | ||||||
|   | |||||||
| @@ -170,7 +170,7 @@ class CalendarSocialApp(Flask): | |||||||
|  |  | ||||||
|         calendar = GregorianCalendar(timestamp.timestamp()) |         calendar = GregorianCalendar(timestamp.timestamp()) | ||||||
|  |  | ||||||
|         return render_template('index.html', calendar=calendar) |         return render_template('index.html', calendar=calendar, user_only=True) | ||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     @route('/register', methods=['POST', 'GET']) |     @route('/register', methods=['POST', 'GET']) | ||||||
| @@ -217,7 +217,7 @@ class CalendarSocialApp(Flask): | |||||||
|         form = EventForm() |         form = EventForm() | ||||||
|  |  | ||||||
|         if form.validate_on_submit(): |         if form.validate_on_submit(): | ||||||
|             event = Event(user=current_user) |             event = Event(profile=current_user.profile) | ||||||
|             form.populate_obj(event) |             form.populate_obj(event) | ||||||
|  |  | ||||||
|             db.session.add(event) |             db.session.add(event) | ||||||
| @@ -255,7 +255,7 @@ class CalendarSocialApp(Flask): | |||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         from .forms import InviteForm |         from .forms import InviteForm | ||||||
|         from .models import db, Event, Invitation, Notification, NotificationAction |         from .models import db, Event | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             event = Event.query.filter(Event.event_uuid == event_uuid).one() |             event = Event.query.filter(Event.event_uuid == event_uuid).one() | ||||||
| @@ -267,15 +267,7 @@ class CalendarSocialApp(Flask): | |||||||
|         form = InviteForm(event) |         form = InviteForm(event) | ||||||
|  |  | ||||||
|         if form.validate_on_submit(): |         if form.validate_on_submit(): | ||||||
|             invite = Invitation(event=event, sender=current_user.profile) |             event.invite(current_user.profile, invitee=form.invitee.data) | ||||||
|             form.populate_obj(invite) |  | ||||||
|             db.session.add(invite) |  | ||||||
|  |  | ||||||
|             notification = Notification(profile=form.invitee.data, |  | ||||||
|                                         actor=current_user.profile, |  | ||||||
|                                         item=event, |  | ||||||
|                                         action=NotificationAction.invite) |  | ||||||
|             db.session.add(notification) |  | ||||||
|  |  | ||||||
|             db.session.commit() |             db.session.commit() | ||||||
|  |  | ||||||
| @@ -300,11 +292,12 @@ class CalendarSocialApp(Flask): | |||||||
|  |  | ||||||
|     @staticmethod |     @staticmethod | ||||||
|     @route('/profile/@<string:username>/follow') |     @route('/profile/@<string:username>/follow') | ||||||
|  |     @login_required | ||||||
|     def follow_user(username): |     def follow_user(username): | ||||||
|         """View for following a user |         """View for following a user | ||||||
|         """ |         """ | ||||||
|  |  | ||||||
|         from .models import db, Profile, User, UserFollow, Notification, NotificationAction |         from .models import db, Profile, User | ||||||
|  |  | ||||||
|         try: |         try: | ||||||
|             profile = Profile.query.join(User).filter(User.username == username).one() |             profile = Profile.query.join(User).filter(User.username == username).one() | ||||||
| @@ -312,16 +305,7 @@ class CalendarSocialApp(Flask): | |||||||
|             abort(404) |             abort(404) | ||||||
|  |  | ||||||
|         if profile.user != current_user: |         if profile.user != current_user: | ||||||
|             follow = UserFollow(follower=current_user.profile, |             profile.follow(follower=current_user.profile) | ||||||
|                                 followed=profile, |  | ||||||
|                                 accepted_at=datetime.utcnow()) |  | ||||||
|             db.session.add(follow) |  | ||||||
|  |  | ||||||
|             notification = Notification(profile=profile, |  | ||||||
|                                         actor=current_user.profile, |  | ||||||
|                                         item=profile, |  | ||||||
|                                         action=NotificationAction.follow) |  | ||||||
|             db.session.add(notification) |  | ||||||
|  |  | ||||||
|             db.session.commit() |             db.session.commit() | ||||||
|  |  | ||||||
| @@ -399,5 +383,83 @@ class CalendarSocialApp(Flask): | |||||||
|  |  | ||||||
|         return render_template('first-steps.html', form=form) |         return render_template('first-steps.html', form=form) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     @route('/edit-profile', methods=['GET', 'POST']) | ||||||
|  |     @login_required | ||||||
|  |     def edit_profile(): | ||||||
|  |         """View for editing one’s profile | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         from .forms import ProfileForm | ||||||
|  |         from .models import db | ||||||
|  |  | ||||||
|  |         form = ProfileForm(current_user.profile) | ||||||
|  |  | ||||||
|  |         if form.validate_on_submit(): | ||||||
|  |             form.populate_obj(current_user.profile) | ||||||
|  |             db.session.add(current_user.profile) | ||||||
|  |             db.session.commit() | ||||||
|  |  | ||||||
|  |             return redirect(url_for('edit_profile')) | ||||||
|  |  | ||||||
|  |         return render_template('profile-edit.html', form=form) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     @route('/all-events') | ||||||
|  |     def all_events(): | ||||||
|  |         """View for listing all available events | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         from .calendar_system.gregorian import GregorianCalendar | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             timestamp = datetime.fromtimestamp(float(request.args.get('date'))) | ||||||
|  |         except TypeError: | ||||||
|  |             timestamp = datetime.utcnow() | ||||||
|  |  | ||||||
|  |         calendar = GregorianCalendar(timestamp.timestamp()) | ||||||
|  |  | ||||||
|  |         return render_template('index.html', calendar=calendar, user_only=False) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     @route('/follow-requests') | ||||||
|  |     @login_required | ||||||
|  |     def follow_requests(): | ||||||
|  |         """View for listing follow requests | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         from .models import UserFollow | ||||||
|  |  | ||||||
|  |         requests = UserFollow.query \ | ||||||
|  |                              .filter(UserFollow.followed == current_user.profile) \ | ||||||
|  |                              .filter(UserFollow.accepted_at.is_(None)) | ||||||
|  |  | ||||||
|  |         return render_template('follow-requests.html', requests=requests) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     @route('/follow-request/<int:follower_id>/accept') | ||||||
|  |     @login_required | ||||||
|  |     def accept_follow(follower_id): | ||||||
|  |         """View for accepting a follow request | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         from .models import db, UserFollow | ||||||
|  |  | ||||||
|  |         try: | ||||||
|  |             req = UserFollow.query \ | ||||||
|  |                             .filter(UserFollow.followed == current_user.profile) \ | ||||||
|  |                             .filter(UserFollow.follower_id == follower_id) \ | ||||||
|  |                             .one() | ||||||
|  |         except NoResultFound: | ||||||
|  |             abort(404) | ||||||
|  |  | ||||||
|  |         if req.accepted_at is None: | ||||||
|  |             req.accept() | ||||||
|  |  | ||||||
|  |             db.session.add(req) | ||||||
|  |             db.session.commit() | ||||||
|  |  | ||||||
|  |         return redirect(url_for('follow_requests')) | ||||||
|  |  | ||||||
|  |  | ||||||
| app = CalendarSocialApp(__name__) | app = CalendarSocialApp(__name__) | ||||||
|   | |||||||
| @@ -330,8 +330,24 @@ class FirstStepsForm(FlaskForm): | |||||||
|     display_name = StringField( |     display_name = StringField( | ||||||
|         label=_('Display name'), |         label=_('Display name'), | ||||||
|         validators=[DataRequired()], |         validators=[DataRequired()], | ||||||
|  |         # pylint: disable=line-too-long | ||||||
|         description=_('This will be shown to other users as your name.  You can use your real name, or any nickname you like.')) |         description=_('This will be shown to other users as your name.  You can use your real name, or any nickname you like.')) | ||||||
|     time_zone = TimezoneField( |     time_zone = TimezoneField( | ||||||
|         label=_('Your time zone'), |         label=_('Your time zone'), | ||||||
|         validators=[DataRequired()], |         validators=[DataRequired()], | ||||||
|         description=_('The start and end times of events will be displayed in this time zone.')) |         description=_('The start and end times of events will be displayed in this time zone.')) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class ProfileForm(FlaskForm): | ||||||
|  |     """Form for editing a user profile | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     display_name = StringField(label=_('Display name'), validators=[DataRequired()]) | ||||||
|  |     locked = BooleanField(label=_('Lock profile')) | ||||||
|  |  | ||||||
|  |     def __init__(self, profile, *args, **kwargs): | ||||||
|  |         kwargs.update({'display_name': profile.display_name}) | ||||||
|  |         kwargs.update({'locked': profile.locked}) | ||||||
|  |         FlaskForm.__init__(self, *args, **kwargs) | ||||||
|  |  | ||||||
|  |         self.profile = profile | ||||||
|   | |||||||
| @@ -246,6 +246,9 @@ class Profile(db.Model):  # pylint: disable=too-few-public-methods | |||||||
|     #: The display name |     #: The display name | ||||||
|     display_name = db.Column(db.Unicode(length=80), nullable=False) |     display_name = db.Column(db.Unicode(length=80), nullable=False) | ||||||
|  |  | ||||||
|  |     #: If locked, a profile cannot be followed without the owner’s consent | ||||||
|  |     locked = db.Column(db.Boolean(), default=False) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def fqn(self): |     def fqn(self): | ||||||
|         """The fully qualified name of the profile |         """The fully qualified name of the profile | ||||||
| @@ -265,7 +268,7 @@ class Profile(db.Model):  # pylint: disable=too-few-public-methods | |||||||
|             domain = '' |             domain = '' | ||||||
|         else: |         else: | ||||||
|             username = self.username |             username = self.username | ||||||
|             domain = '@' + self.domain |             domain = f'@{self.domain}' | ||||||
|  |  | ||||||
|         return f'<Profile {self.id}(@{username}{domain})>' |         return f'<Profile {self.id}(@{username}{domain})>' | ||||||
|  |  | ||||||
| @@ -290,7 +293,8 @@ class Profile(db.Model):  # pylint: disable=too-few-public-methods | |||||||
|  |  | ||||||
|         return Profile.query \ |         return Profile.query \ | ||||||
|             .join(UserFollow.followed) \ |             .join(UserFollow.followed) \ | ||||||
|             .filter(UserFollow.follower == self) |             .filter(UserFollow.follower == self) \ | ||||||
|  |             .filter(UserFollow.accepted_at.isnot(None)) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def follower_list(self): |     def follower_list(self): | ||||||
| @@ -303,7 +307,23 @@ class Profile(db.Model):  # pylint: disable=too-few-public-methods | |||||||
|  |  | ||||||
|         return Profile.query \ |         return Profile.query \ | ||||||
|             .join(UserFollow.follower) \ |             .join(UserFollow.follower) \ | ||||||
|             .filter(UserFollow.followed == self) |             .filter(UserFollow.followed == self) \ | ||||||
|  |             .filter(UserFollow.accepted_at.isnot(None)) | ||||||
|  |  | ||||||
|  |     @property | ||||||
|  |     def friend_list(self): | ||||||
|  |         """List of friends (ie. where both profiles follow each other) | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         # This will always be empty for remote profiles | ||||||
|  |         if not self.user: | ||||||
|  |             return [] | ||||||
|  |  | ||||||
|  |         reverse = db.aliased(UserFollow) | ||||||
|  |         return UserFollow.query \ | ||||||
|  |                          .filter(UserFollow.follower == self) \ | ||||||
|  |                          .join(reverse, UserFollow.followed == reverse.follower) \ | ||||||
|  |                          .filter(UserFollow.follower == reverse.followed) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def url(self): |     def url(self): | ||||||
| @@ -317,6 +337,48 @@ class Profile(db.Model):  # pylint: disable=too-few-public-methods | |||||||
|  |  | ||||||
|         return NotImplemented |         return NotImplemented | ||||||
|  |  | ||||||
|  |     def follow(self, follower): | ||||||
|  |         """Make ``follower`` follow this profile | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         if not isinstance(follower, Profile): | ||||||
|  |             raise TypeError('Folloer must be a Profile object') | ||||||
|  |  | ||||||
|  |         timestamp = None if self.locked else datetime.utcnow() | ||||||
|  |  | ||||||
|  |         user_follow = UserFollow(follower=follower, followed=self, accepted_at=timestamp) | ||||||
|  |         db.session.add(user_follow) | ||||||
|  |         notification = self.notify(follower, self, NotificationAction.follow) | ||||||
|  |  | ||||||
|  |         db.session.add(notification) | ||||||
|  |  | ||||||
|  |         return user_follow | ||||||
|  |  | ||||||
|  |     def notify(self, actor, item, action): | ||||||
|  |         """Notify this profile about ``action`` on ``item`` by ``actor`` | ||||||
|  |  | ||||||
|  |         :param actor: the actor who generated the notification | ||||||
|  |         :type actor: Profile | ||||||
|  |         :param item: the item ``action`` was performed on | ||||||
|  |         :type item: any | ||||||
|  |         :param action: the type of the action | ||||||
|  |         :type action: NotificationAction, str | ||||||
|  |         :raises TypeError: if ``actor`` is not a `Profile` object | ||||||
|  |         :returns: the generated notification.  It is already added to the database session, but | ||||||
|  |             not committed | ||||||
|  |         :rtype: Notification | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         if not isinstance(actor, Profile): | ||||||
|  |             raise TypeError('actor must be a Profile instance') | ||||||
|  |  | ||||||
|  |         if isinstance(action, str): | ||||||
|  |             action = NotificationAction[action] | ||||||
|  |  | ||||||
|  |         notification = Notification(profile=self, actor=actor, item=item, action=action) | ||||||
|  |  | ||||||
|  |         return notification | ||||||
|  |  | ||||||
|  |  | ||||||
| class Event(db.Model): | class Event(db.Model): | ||||||
|     """Database model for events |     """Database model for events | ||||||
| @@ -400,6 +462,20 @@ class Event(db.Model): | |||||||
|  |  | ||||||
|         return url_for('event_details', event_uuid=self.event_uuid) |         return url_for('event_details', event_uuid=self.event_uuid) | ||||||
|  |  | ||||||
|  |     def invite(self, inviter, invited): | ||||||
|  |         """Invite ``invited`` to the event | ||||||
|  |  | ||||||
|  |         The invitation will arrive from ``inviter``. | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         invite = Invitation(event=self, sender=inviter, invitee=invited) | ||||||
|  |         db.session.add(invite) | ||||||
|  |  | ||||||
|  |         notification = invited.notify(inviter, self, NotificationAction.invite) | ||||||
|  |         db.session.add(notification) | ||||||
|  |  | ||||||
|  |         return invite | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserSetting(db.Model):  # pylint: disable=too-few-public-methods | class UserSetting(db.Model):  # pylint: disable=too-few-public-methods | ||||||
|     """Database model for user settings |     """Database model for user settings | ||||||
| @@ -543,6 +619,12 @@ class UserFollow(db.Model):  # pylint: disable=too-few-public-methods | |||||||
|     #: The timestamp when the follow was accepted |     #: The timestamp when the follow was accepted | ||||||
|     accepted_at = db.Column(db.DateTime(), nullable=True) |     accepted_at = db.Column(db.DateTime(), nullable=True) | ||||||
|  |  | ||||||
|  |     def accept(self): | ||||||
|  |         """Accept this follow request | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         self.accepted_at = datetime.utcnow() | ||||||
|  |  | ||||||
|  |  | ||||||
| class Notification(db.Model): | class Notification(db.Model): | ||||||
|     """Database model for notifications |     """Database model for notifications | ||||||
|   | |||||||
							
								
								
									
										13
									
								
								calsocial/templates/follow-requests.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								calsocial/templates/follow-requests.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,13 @@ | |||||||
|  | {% extends 'base.html' %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <h2>{% trans %}Follow requests{% endtrans %}</h2> | ||||||
|  | <ul> | ||||||
|  |     {% for req in requests %} | ||||||
|  |     <li> | ||||||
|  |         {{ req.follower }} | ||||||
|  |         <a href="{{ url_for('accept_follow', follower_id=req.follower_id) }}">{% trans %}Accept{% endtrans %}</a> | ||||||
|  |     </li> | ||||||
|  |     {% endfor %} | ||||||
|  | </ul> | ||||||
|  | {% endblock content %} | ||||||
| @@ -120,7 +120,7 @@ | |||||||
|     {%- endif %} |     {%- endif %} | ||||||
|             <td class="{% if day.month != calendar.timestamp.month %} other-month{% endif %}{% if day.date() == now.date() %} today{% endif %}"> |             <td class="{% if day.month != calendar.timestamp.month %} other-month{% endif %}{% if day.date() == now.date() %} today{% endif %}"> | ||||||
|                 <span class="day-num">{{ day.day }}</span> |                 <span class="day-num">{{ day.day }}</span> | ||||||
|     {% for event in calendar.day_events(day, user=current_user) %} |     {% for event in calendar.day_events(day, user=current_user if user_only else none) %} | ||||||
|                 <a href="{{ url_for('event_details', event_uuid=event.event_uuid) }}" class="event"> |                 <a href="{{ url_for('event_details', event_uuid=event.event_uuid) }}" class="event"> | ||||||
|                     {{ event.start_time_for_user(current_user).strftime('%H:%M') }}–{{ event.end_time_for_user(current_user).strftime('%H:%M') }} |                     {{ event.start_time_for_user(current_user).strftime('%H:%M') }}–{{ event.end_time_for_user(current_user).strftime('%H:%M') }} | ||||||
|                     {{ event.title }} |                     {{ event.title }} | ||||||
|   | |||||||
| @@ -2,7 +2,10 @@ | |||||||
|  |  | ||||||
| {% block content %} | {% block content %} | ||||||
| <h1> | <h1> | ||||||
|     {{ profile.name }} |     {% if profile.locked %} | ||||||
|  |     [locked] | ||||||
|  |     {% endif %} | ||||||
|  |     {{ profile.display_name }} | ||||||
|     <small>@{{ profile.user.username}}</small> |     <small>@{{ profile.user.username}}</small> | ||||||
| </h1> | </h1> | ||||||
|     {% if profile.user != current_user %} |     {% if profile.user != current_user %} | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								calsocial/templates/profile-edit.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								calsocial/templates/profile-edit.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | {% extends 'settings-base.html' %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | {{ super() }} | ||||||
|  | <h2>{% trans %}Edit profile{% endtrans %}</h2> | ||||||
|  | <form method="post"> | ||||||
|  |     {{ form.hidden_tag() }} | ||||||
|  |     {{ form.errors }} | ||||||
|  |  | ||||||
|  |     {{ form.display_name.errors }} | ||||||
|  |     {{ form.display_name.label }} | ||||||
|  |     {{ form.display_name }} | ||||||
|  |     <br> | ||||||
|  |  | ||||||
|  |     {{ form.locked.errors }} | ||||||
|  |     {{ form.locked.label }} | ||||||
|  |     {{ form.locked}} | ||||||
|  |     <br> | ||||||
|  |  | ||||||
|  |     <button type="submit">{% trans %}Save{% endtrans %}</button> | ||||||
|  | </form> | ||||||
|  | {% endblock content %} | ||||||
							
								
								
									
										10
									
								
								calsocial/templates/settings-base.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								calsocial/templates/settings-base.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,10 @@ | |||||||
|  | {% extends 'base.html' %} | ||||||
|  |  | ||||||
|  | {% block content %} | ||||||
|  | <nav> | ||||||
|  |     <ul> | ||||||
|  |         <li><a href="{{ url_for('edit_profile') }}">{% trans %}Edit profile{% endtrans %}</a></li> | ||||||
|  |         <li><a href="{{ url_for('settings') }}">{% trans %}Settings{% endtrans %}</a></li> | ||||||
|  |     </ul> | ||||||
|  | </nav> | ||||||
|  | {% endblock %} | ||||||
| @@ -1,6 +1,8 @@ | |||||||
| {% extends 'base.html' %} | {% extends 'settings-base.html' %} | ||||||
|  |  | ||||||
| {% block content %} | {% block content %} | ||||||
|  | {{ super() }} | ||||||
|  | <h2>{% trans %}Settings{% endtrans %}</h2> | ||||||
| <form method="post"> | <form method="post"> | ||||||
|     {{ form.hidden_tag() }} |     {{ form.hidden_tag() }} | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										72
									
								
								tests/helpers.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								tests/helpers.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | |||||||
|  | # Calendar.social | ||||||
|  | # Copyright (C) 2018  Gergely Polonkai | ||||||
|  | # | ||||||
|  | # This program is free software: you can redistribute it and/or modify | ||||||
|  | # it under the terms of the GNU Affero General Public License as published by | ||||||
|  | # the Free Software Foundation, either version 3 of the License, or | ||||||
|  | # (at your option) any later version. | ||||||
|  | # | ||||||
|  | # This program is distributed in the hope that it will be useful, | ||||||
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | # GNU Affero General Public License for more details. | ||||||
|  | # | ||||||
|  | # You should have received a copy of the GNU Affero General Public License | ||||||
|  | # along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | """Helper functions and fixtures for testing | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import pytest | ||||||
|  |  | ||||||
|  | import calsocial | ||||||
|  | from calsocial.models import db | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def configure_app(): | ||||||
|  |     """Set default configuration values for testing | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     calsocial.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' | ||||||
|  |     calsocial.app.config['TESTING'] = True | ||||||
|  |     calsocial.app.config['WTF_CSRF_ENABLED'] = False | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture | ||||||
|  | def client(): | ||||||
|  |     """Fixture that provides a Flask test client | ||||||
|  |     """ | ||||||
|  |     configure_app() | ||||||
|  |     client = calsocial.app.test_client() | ||||||
|  |  | ||||||
|  |     with calsocial.app.app_context(): | ||||||
|  |         db.create_all() | ||||||
|  |  | ||||||
|  |     yield client | ||||||
|  |  | ||||||
|  |     with calsocial.app.app_context(): | ||||||
|  |         db.drop_all() | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def login(client, username, password, no_redirect=False): | ||||||
|  |     """Login with the specified username and password | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     return client.post('/login', | ||||||
|  |                        data={'email': username, 'password': password}, | ||||||
|  |                        follow_redirects=not no_redirect) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @pytest.fixture | ||||||
|  | def database(): | ||||||
|  |     """Fixture to provide all database tables in an active application context | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     configure_app() | ||||||
|  |  | ||||||
|  |     with calsocial.app.app_context(): | ||||||
|  |         db.create_all() | ||||||
|  |  | ||||||
|  |         yield db | ||||||
|  |  | ||||||
|  |         db.drop_all() | ||||||
| @@ -1,23 +1,23 @@ | |||||||
| import pytest | # Calendar.social | ||||||
|  | # Copyright (C) 2018  Gergely Polonkai | ||||||
|  | # | ||||||
|  | # This program is free software: you can redistribute it and/or modify | ||||||
|  | # it under the terms of the GNU Affero General Public License as published by | ||||||
|  | # the Free Software Foundation, either version 3 of the License, or | ||||||
|  | # (at your option) any later version. | ||||||
|  | # | ||||||
|  | # This program is distributed in the hope that it will be useful, | ||||||
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | # GNU Affero General Public License for more details. | ||||||
|  | # | ||||||
|  | # You should have received a copy of the GNU Affero General Public License | ||||||
|  | # along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
| import calsocial | """General tests for Calendar.social | ||||||
| from calsocial.models import db, User | """ | ||||||
|  |  | ||||||
|  | from helpers import client | ||||||
| @pytest.fixture |  | ||||||
| def client(): |  | ||||||
|     calsocial.app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' |  | ||||||
|     calsocial.app.config['TESTING'] = True |  | ||||||
|     calsocial.app.config['WTF_CSRF_ENABLED'] = False |  | ||||||
|     client = calsocial.app.test_client() |  | ||||||
|  |  | ||||||
|     with calsocial.app.app_context(): |  | ||||||
|         db.create_all() |  | ||||||
|  |  | ||||||
|     yield client |  | ||||||
|  |  | ||||||
|     with calsocial.app.app_context(): |  | ||||||
|         db.drop_all() |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_index_no_login(client): | def test_index_no_login(client): | ||||||
| @@ -26,96 +26,3 @@ def test_index_no_login(client): | |||||||
|  |  | ||||||
|     page = client.get('/') |     page = client.get('/') | ||||||
|     assert b'Welcome to Calendar.social' in page.data |     assert b'Welcome to Calendar.social' in page.data | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_register_page(client): |  | ||||||
|     """Test the registration page |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     page = client.get('/register') |  | ||||||
|     assert b'Register</button>' in page.data |  | ||||||
|  |  | ||||||
| def test_register_post_empty(client): |  | ||||||
|     """Test sending empty registration data |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     page = client.post('/register', data={}) |  | ||||||
|     assert b'This field is required' in page.data |  | ||||||
|  |  | ||||||
| def test_register_invalid_email(client): |  | ||||||
|     """Test sending an invalid email address |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     page = client.post('/register', data={ |  | ||||||
|         'username': 'test', |  | ||||||
|         'email': 'test', |  | ||||||
|         'password': 'password', |  | ||||||
|         'password_retype': 'password', |  | ||||||
|     }) |  | ||||||
|     assert b'Invalid email address' in page.data |  | ||||||
|  |  | ||||||
| def test_register_password_mismatch(client): |  | ||||||
|     """Test sending different password for registration |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     page = client.post('/register', data={ |  | ||||||
|         'username': 'test', |  | ||||||
|         'email': 'test@example.com', |  | ||||||
|         'password': 'password', |  | ||||||
|         'password_retype': 'something', |  | ||||||
|     }) |  | ||||||
|     assert b'The two passwords must match' in page.data |  | ||||||
|  |  | ||||||
| def test_register(client): |  | ||||||
|     """Test user registration |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     page = client.post('/register', data={ |  | ||||||
|         'username': 'test', |  | ||||||
|         'email': 'test@example.com', |  | ||||||
|         'password': 'password', |  | ||||||
|         'password_retype': 'password', |  | ||||||
|     }) |  | ||||||
|     print(page.data) |  | ||||||
|     assert page.status_code == 302 |  | ||||||
|     assert page.location == 'http://localhost/' |  | ||||||
|  |  | ||||||
|     with calsocial.app.app_context(): |  | ||||||
|         user = User.query.one() |  | ||||||
|  |  | ||||||
|     assert user.username == 'test' |  | ||||||
|     assert user.email == 'test@example.com' |  | ||||||
|  |  | ||||||
| def test_register_existing_username(client): |  | ||||||
|     """Test registering an existing username |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     with calsocial.app.app_context(): |  | ||||||
|         user = User(username='test', email='test@example.com') |  | ||||||
|         db.session.add(user) |  | ||||||
|         db.session.commit() |  | ||||||
|  |  | ||||||
|     page = client.post('/register', data={ |  | ||||||
|         'username': 'test', |  | ||||||
|         'email': 'test2@example.com', |  | ||||||
|         'password': 'password', |  | ||||||
|         'password_retype': 'password', |  | ||||||
|     }) |  | ||||||
|     assert b'This username is not available' in page.data |  | ||||||
|  |  | ||||||
| def test_register_existing_email(client): |  | ||||||
|     """Test registering an existing email address |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     with calsocial.app.app_context(): |  | ||||||
|         user = User(username='test', email='test@example.com') |  | ||||||
|         db.session.add(user) |  | ||||||
|         db.session.commit() |  | ||||||
|  |  | ||||||
|     page = client.post('/register', data={ |  | ||||||
|         'username': 'tester', |  | ||||||
|         'email': 'test@example.com', |  | ||||||
|         'password': 'password', |  | ||||||
|         'password_retype': 'password', |  | ||||||
|     }) |  | ||||||
|     assert b'This email address can not be used' in page.data |  | ||||||
|   | |||||||
							
								
								
									
										117
									
								
								tests/test_follow.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								tests/test_follow.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,117 @@ | |||||||
|  | # Calendar.social | ||||||
|  | # Copyright (C) 2018  Gergely Polonkai | ||||||
|  | # | ||||||
|  | # This program is free software: you can redistribute it and/or modify | ||||||
|  | # it under the terms of the GNU Affero General Public License as published by | ||||||
|  | # the Free Software Foundation, either version 3 of the License, or | ||||||
|  | # (at your option) any later version. | ||||||
|  | # | ||||||
|  | # This program is distributed in the hope that it will be useful, | ||||||
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | # GNU Affero General Public License for more details. | ||||||
|  | # | ||||||
|  | # You should have received a copy of the GNU Affero General Public License | ||||||
|  | # along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | """Profile related tests for Calendar.social | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import calsocial | ||||||
|  | from calsocial.models import db, Notification, NotificationAction, Profile, User, UserFollow | ||||||
|  |  | ||||||
|  | from helpers import client, database, login | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_profile_follow(database): | ||||||
|  |     """Test the Profile.follow() method | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     follower_user = User(username='follower', | ||||||
|  |                          email='follower@example.com', | ||||||
|  |                          password='passworder', | ||||||
|  |                          active=True) | ||||||
|  |     followed_user = User(username='followed', email='followed@example.com') | ||||||
|  |     follower = Profile(display_name='Follower', user=follower_user) | ||||||
|  |     followed = Profile(display_name='Followed', user=followed_user) | ||||||
|  |     db.session.add_all([follower, followed]) | ||||||
|  |     db.session.commit() | ||||||
|  |  | ||||||
|  |     user_follow = followed.follow(follower=follower) | ||||||
|  |     db.session.commit() | ||||||
|  |  | ||||||
|  |     # The new follower record should have the fields set correctly | ||||||
|  |     assert user_follow.followed == followed | ||||||
|  |     assert user_follow.follower == follower | ||||||
|  |  | ||||||
|  |     # There should be a notification about the follow | ||||||
|  |     notification = Notification.query.one() | ||||||
|  |  | ||||||
|  |     assert notification.actor == follower | ||||||
|  |     assert notification.item == followed | ||||||
|  |     assert notification.action == NotificationAction.follow | ||||||
|  |  | ||||||
|  |     assert follower in followed.follower_list | ||||||
|  |     assert followed in follower.followed_list | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_follow_ui(client): | ||||||
|  |     """Test following on the web interface | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     with calsocial.app.app_context(): | ||||||
|  |         follower_user = User(username='follower', | ||||||
|  |                              email='follower@example.com', | ||||||
|  |                              password='passworder', | ||||||
|  |                              active=True) | ||||||
|  |         followed_user = User(username='followed', email='followed@example.com') | ||||||
|  |         follower = Profile(display_name='Follower', user=follower_user) | ||||||
|  |         followed = Profile(display_name='Followed', user=followed_user) | ||||||
|  |         db.session.add_all([follower, followed]) | ||||||
|  |         db.session.commit() | ||||||
|  |  | ||||||
|  |     login(client, 'follower', 'passworder') | ||||||
|  |  | ||||||
|  |     page = client.get('/profile/@followed/follow') | ||||||
|  |     assert page.status_code == 302 | ||||||
|  |     assert page.location == 'http://localhost/profile/%40followed' | ||||||
|  |  | ||||||
|  |     with calsocial.app.app_context(): | ||||||
|  |         db.session.add_all([follower, followed]) | ||||||
|  |         follow = UserFollow.query.one() | ||||||
|  |         assert follow.follower == follower | ||||||
|  |         assert follow.followed == followed | ||||||
|  |         assert follow.accepted_at is not None | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_locked_profile(database): | ||||||
|  |     """Test following a locked profile | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     follower_user = User(username='follower', | ||||||
|  |                          email='follower@example.com', | ||||||
|  |                          password='passworder', | ||||||
|  |                          active=True) | ||||||
|  |     followed_user = User(username='followed', email='followed@example.com') | ||||||
|  |     follower = Profile(display_name='Follower', user=follower_user) | ||||||
|  |     followed = Profile(display_name='Followed', user=followed_user, locked=True) | ||||||
|  |     db.session.add_all([follower, followed]) | ||||||
|  |     db.session.commit() | ||||||
|  |  | ||||||
|  |     user_follow = followed.follow(follower=follower) | ||||||
|  |     db.session.commit() | ||||||
|  |  | ||||||
|  |     # The new follower record should have the fields set correctly | ||||||
|  |     assert user_follow.followed == followed | ||||||
|  |     assert user_follow.follower == follower | ||||||
|  |     assert not user_follow.accepted_at | ||||||
|  |  | ||||||
|  |     # There should be a notification about the follow | ||||||
|  |     notification = Notification.query.one() | ||||||
|  |  | ||||||
|  |     assert notification.actor == follower | ||||||
|  |     assert notification.item == followed | ||||||
|  |     assert notification.action == NotificationAction.follow | ||||||
|  |  | ||||||
|  |     assert follower not in followed.follower_list | ||||||
|  |     assert followed not in follower.followed_list | ||||||
							
								
								
									
										84
									
								
								tests/test_login.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								tests/test_login.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,84 @@ | |||||||
|  | # Calendar.social | ||||||
|  | # Copyright (C) 2018  Gergely Polonkai | ||||||
|  | # | ||||||
|  | # This program is free software: you can redistribute it and/or modify | ||||||
|  | # it under the terms of the GNU Affero General Public License as published by | ||||||
|  | # the Free Software Foundation, either version 3 of the License, or | ||||||
|  | # (at your option) any later version. | ||||||
|  | # | ||||||
|  | # This program is distributed in the hope that it will be useful, | ||||||
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | # GNU Affero General Public License for more details. | ||||||
|  | # | ||||||
|  | # You should have received a copy of the GNU Affero General Public License | ||||||
|  | # along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | """General tests for Calendar.social | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import calsocial | ||||||
|  | from calsocial.models import db, User | ||||||
|  |  | ||||||
|  | from helpers import client, login | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_index_no_login(client): | ||||||
|  |     """Test the main page without logging in | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     page = client.get('/') | ||||||
|  |     assert b'Welcome to Calendar.social' in page.data | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_login_invalid_user(client): | ||||||
|  |     """Test logging in with a non-existing user | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     page = login(client, 'username', 'password') | ||||||
|  |     assert b'Specified user does not exist' in page.data | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_login_bad_password(client): | ||||||
|  |     """Test logging in with a bad password | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     with calsocial.app.app_context(): | ||||||
|  |         user = User(username='test', email='test@example.com', password='password') | ||||||
|  |         db.session.add(user) | ||||||
|  |         db.session.commit() | ||||||
|  |  | ||||||
|  |     page = login(client, 'test', password='else') | ||||||
|  |     assert b'Invalid password' in page.data | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_login_email(client): | ||||||
|  |     """Test logging in with the email address instead of the username | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     with calsocial.app.app_context(): | ||||||
|  |         user = User(username='test', email='test@example.com', password='password', active=True) | ||||||
|  |         db.session.add(user) | ||||||
|  |         db.session.commit() | ||||||
|  |  | ||||||
|  |     page = login(client, 'test@example.com', password='password') | ||||||
|  |     assert b'Logged in as ' in page.data | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_login_first_steps(client): | ||||||
|  |     """Test logging in with a new user | ||||||
|  |  | ||||||
|  |     They must be redirected to the first login page | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     with calsocial.app.app_context(): | ||||||
|  |         user = User(username='test', email='test@example.com', password='password', active=True) | ||||||
|  |         db.session.add(user) | ||||||
|  |         db.session.commit() | ||||||
|  |  | ||||||
|  |     page = login(client, 'test', password='password', no_redirect=True) | ||||||
|  |     # First, we must be redirected to the main page | ||||||
|  |     assert page.location == 'http://localhost/' | ||||||
|  |  | ||||||
|  |     page = client.get('/') | ||||||
|  |     assert page.location == 'http://localhost/first-steps' | ||||||
							
								
								
									
										116
									
								
								tests/test_register.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										116
									
								
								tests/test_register.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,116 @@ | |||||||
|  | # Calendar.social | ||||||
|  | # Copyright (C) 2018  Gergely Polonkai | ||||||
|  | # | ||||||
|  | # This program is free software: you can redistribute it and/or modify | ||||||
|  | # it under the terms of the GNU Affero General Public License as published by | ||||||
|  | # the Free Software Foundation, either version 3 of the License, or | ||||||
|  | # (at your option) any later version. | ||||||
|  | # | ||||||
|  | # This program is distributed in the hope that it will be useful, | ||||||
|  | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||||||
|  | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | ||||||
|  | # GNU Affero General Public License for more details. | ||||||
|  | # | ||||||
|  | # You should have received a copy of the GNU Affero General Public License | ||||||
|  | # along with this program.  If not, see <https://www.gnu.org/licenses/>. | ||||||
|  |  | ||||||
|  | """General tests for Calendar.social | ||||||
|  | """ | ||||||
|  |  | ||||||
|  | import calsocial | ||||||
|  | from calsocial.models import db, User | ||||||
|  |  | ||||||
|  | from helpers import client | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def test_register_page(client): | ||||||
|  |     """Test the registration page | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     page = client.get('/register') | ||||||
|  |     assert b'Register</button>' in page.data | ||||||
|  |  | ||||||
|  | def test_register_post_empty(client): | ||||||
|  |     """Test sending empty registration data | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     page = client.post('/register', data={}) | ||||||
|  |     assert b'This field is required' in page.data | ||||||
|  |  | ||||||
|  | def test_register_invalid_email(client): | ||||||
|  |     """Test sending an invalid email address | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     page = client.post('/register', data={ | ||||||
|  |         'username': 'test', | ||||||
|  |         'email': 'test', | ||||||
|  |         'password': 'password', | ||||||
|  |         'password_retype': 'password', | ||||||
|  |     }) | ||||||
|  |     assert b'Invalid email address' in page.data | ||||||
|  |  | ||||||
|  | def test_register_password_mismatch(client): | ||||||
|  |     """Test sending different password for registration | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     page = client.post('/register', data={ | ||||||
|  |         'username': 'test', | ||||||
|  |         'email': 'test@example.com', | ||||||
|  |         'password': 'password', | ||||||
|  |         'password_retype': 'something', | ||||||
|  |     }) | ||||||
|  |     assert b'The two passwords must match' in page.data | ||||||
|  |  | ||||||
|  | def test_register(client): | ||||||
|  |     """Test user registration | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     page = client.post('/register', data={ | ||||||
|  |         'username': 'test', | ||||||
|  |         'email': 'test@example.com', | ||||||
|  |         'password': 'password', | ||||||
|  |         'password_retype': 'password', | ||||||
|  |     }) | ||||||
|  |     print(page.data) | ||||||
|  |     assert page.status_code == 302 | ||||||
|  |     assert page.location == 'http://localhost/' | ||||||
|  |  | ||||||
|  |     with calsocial.app.app_context(): | ||||||
|  |         user = User.query.one() | ||||||
|  |  | ||||||
|  |     assert user.username == 'test' | ||||||
|  |     assert user.email == 'test@example.com' | ||||||
|  |  | ||||||
|  | def test_register_existing_username(client): | ||||||
|  |     """Test registering an existing username | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     with calsocial.app.app_context(): | ||||||
|  |         user = User(username='test', email='test@example.com') | ||||||
|  |         db.session.add(user) | ||||||
|  |         db.session.commit() | ||||||
|  |  | ||||||
|  |     page = client.post('/register', data={ | ||||||
|  |         'username': 'test', | ||||||
|  |         'email': 'test2@example.com', | ||||||
|  |         'password': 'password', | ||||||
|  |         'password_retype': 'password', | ||||||
|  |     }) | ||||||
|  |     assert b'This username is not available' in page.data | ||||||
|  |  | ||||||
|  | def test_register_existing_email(client): | ||||||
|  |     """Test registering an existing email address | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     with calsocial.app.app_context(): | ||||||
|  |         user = User(username='test', email='test@example.com') | ||||||
|  |         db.session.add(user) | ||||||
|  |         db.session.commit() | ||||||
|  |  | ||||||
|  |     page = client.post('/register', data={ | ||||||
|  |         'username': 'tester', | ||||||
|  |         'email': 'test@example.com', | ||||||
|  |         'password': 'password', | ||||||
|  |         'password_retype': 'password', | ||||||
|  |     }) | ||||||
|  |     assert b'This email address can not be used' in page.data | ||||||
		Reference in New Issue
	
	Block a user