Commit 4c37e297 authored by Sébastiaan Versteeg's avatar Sébastiaan Versteeg

Merge branch 'feature/blacked' into 'master'

Black all code

See merge request !1453
parents 79e50f30 489bcfb2
......@@ -22,7 +22,7 @@ codestyle:
before_script:
- poetry install --no-interaction
script:
- poetry run flake8 website
- black --check .
# Check for obsolete translations in .po files (starting with `#~`).
- cd website
- grep --include="*.po" --files-with-matches --recursive "^#~" && exit 1 || echo "No obsolete translations found."
......
......@@ -28,9 +28,8 @@ Testing and linting
You can use [`pyenv`](https://github.com/pyenv/pyenv) (on Unix systems) to test in different python
environments.
The linter can be run in the `poetry shell` or by running
poetry run flake8 website
All code has to be run through [`black`](https://github.com/psf/black) before being committed. To black the code before committing make run `black` one the base directory of this project.
If you want to integrate `black` with your editor look in the [`black` docs](https://black.readthedocs.io/en/stable/editor_integration.html). On linux you can find the black executable in `~/.cache/poety/virtualenvs/<your env>/bin/black`.
There are a range of tests that can be run:
......
......@@ -20,7 +20,7 @@
import os
import sys
sys.path.insert(0, os.path.abspath('../website'))
sys.path.insert(0, os.path.abspath("../website"))
import django
from django.conf import settings # noqa
......@@ -43,23 +43,23 @@ django.setup()
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.doctest',
'sphinx.ext.mathjax',
'sphinx.ext.viewcode',
'sphinx.ext.intersphinx',
'sphinx.ext.graphviz',
'recommonmark',
"sphinx.ext.autodoc",
"sphinx.ext.doctest",
"sphinx.ext.mathjax",
"sphinx.ext.viewcode",
"sphinx.ext.intersphinx",
"sphinx.ext.graphviz",
"recommonmark",
]
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
templates_path = ["_templates"]
# The master toctree document.
master_doc = 'index'
master_doc = "index"
# General information about the project.
project = 'Concrexit'
project = "Concrexit"
copyright = "2017--2019, Technicie, Studievereniging Thalia"
author = "Technicie, Studievereniging Thalia"
......@@ -68,9 +68,9 @@ author = "Technicie, Studievereniging Thalia"
# built documents.
#
# The short X.Y version.
version = ''
version = ""
# The full version, including alpha/beta/rc tags.
release = ''
release = ""
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
......@@ -82,23 +82,23 @@ language = None
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This patterns also effect to html_static_path and html_extra_path
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
pygments_style = "sphinx"
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# default domain:
primary_domain = 'py'
primary_domain = "py"
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = 'alabaster'
html_theme = "alabaster"
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
......@@ -109,31 +109,28 @@ html_theme = 'alabaster'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
#html_static_path = ['_static']
# html_static_path = ['_static']
# -- Options for HTMLHelp output ------------------------------------------
# Output file base name for HTML help builder.
htmlhelp_basename = 'Concrexitdoc'
htmlhelp_basename = "Concrexitdoc"
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
'maxlistdepth': '10'
"maxlistdepth": "10"
# The paper size ('letterpaper' or 'a4paper').
#
# 'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#
# 'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#
# 'preamble': '',
# Latex figure (float) alignment
#
# 'figure_align': 'htbp',
......@@ -143,8 +140,13 @@ latex_elements = {
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'Concrexit.tex', 'Concrexit Documentation',
'Thalia Technicie', 'manual'),
(
master_doc,
"Concrexit.tex",
"Concrexit Documentation",
"Thalia Technicie",
"manual",
),
]
......@@ -152,10 +154,7 @@ latex_documents = [
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'concrexit', 'Concrexit Documentation',
[author], 1)
]
man_pages = [(master_doc, "concrexit", "Concrexit Documentation", [author], 1)]
# -- Options for Texinfo output -------------------------------------------
......@@ -164,35 +163,43 @@ man_pages = [
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'Concrexit', 'Concrexit Documentation',
author, 'Concrexit', "Thalia's website",
'Django Applications'),
(
master_doc,
"Concrexit",
"Concrexit Documentation",
author,
"Concrexit",
"Thalia's website",
"Django Applications",
),
]
# -- Options for autodoc --------------------------------------------------
# Default options for autodoc
autodoc_default_options = {
'members': True,
'undoc-members': True,
"members": True,
"undoc-members": True,
}
# We need to mock the modules for the sphinx build in the docker.
autodoc_mock_imports = ['factory', 'pydenticon', 'faker']
autodoc_mock_imports = ["factory", "pydenticon", "faker"]
# -- Options for doctest --------------------------------------------------
# Disable doctests in normal strings
doctest_test_doctest_blocks = ''
doctest_test_doctest_blocks = ""
# -- intersphinx ---
intersphinx_mapping = {
'python': ('https://docs.python.org/3.7', None),
'django': ('https://docs.djangoproject.com/en/2.2/',
'https://docs.djangoproject.com/en/2.2/_objects/'),
"python": ("https://docs.python.org/3.7", None),
"django": (
"https://docs.djangoproject.com/en/2.2/",
"https://docs.djangoproject.com/en/2.2/_objects/",
),
}
# -- Supress warnings ---
suppress_warnings = [
'image.nonlocal_uri',
"image.nonlocal_uri",
]
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -39,12 +39,15 @@ docs = ["recommonmark", "sphinx"]
[tool.poetry.dev-dependencies]
django-template-check = "~0.3.1"
factory_boy = "~2.12"
flake8 = "~3.7"
pydenticon = "~0.3.1"
pylint = "~2.4"
pylint-django = "~2.0"
Faker = "~3.0"
coverage = "~4.5"
black = "~19.10b0"
[tool.black]
exclude = '(/(\.eggs|\.git|\.tox)/|migrations)'
[build-system]
requires = ["poetry>=0.12"]
......
This diff is collapsed.
......@@ -9,8 +9,7 @@ class NextCloudPermission(permissions.BasePermission):
"""
def has_permission(self, request, view):
if 'HTTP_AUTHORIZATION' in request.META:
token = request.META['HTTP_AUTHORIZATION']
return token == ('Secret ' +
settings.ACTIVEMEMBERS_NEXTCLOUD_API_SECRET)
if "HTTP_AUTHORIZATION" in request.META:
token = request.META["HTTP_AUTHORIZATION"]
return token == ("Secret " + settings.ACTIVEMEMBERS_NEXTCLOUD_API_SECRET)
return request.user.is_superuser
......@@ -7,17 +7,17 @@ from members.models import Member
class NextCloudMemberSerializer(serializers.ModelSerializer):
class Meta:
model = Member
fields = ('pk', 'username', 'first_name',
'last_name', 'is_superuser', 'email')
fields = ("pk", "username", "first_name", "last_name", "is_superuser", "email")
class NextCloudGroupSerializer(serializers.ModelSerializer):
class Meta:
model = MemberGroup
fields = ('pk', 'name', 'members')
fields = ("pk", "name", "members")
members = serializers.SerializerMethodField()
def get_members(self, obj):
return (MemberGroupMembership.active_objects.filter(group=obj)
.values_list('member__username', flat=True))
return MemberGroupMembership.active_objects.filter(group=obj).values_list(
"member__username", flat=True
)
......@@ -3,8 +3,6 @@ from django.urls import path
from activemembers.api import views
urlpatterns = [
path('activemembers/nextcloud/users/',
views.NextCloudUsersView.as_view()),
path('activemembers/nextcloud/groups/',
views.NextCloudGroupsView.as_view()),
path("activemembers/nextcloud/users/", views.NextCloudUsersView.as_view()),
path("activemembers/nextcloud/groups/", views.NextCloudGroupsView.as_view()),
]
......@@ -8,7 +8,7 @@ from rest_framework.generics import ListAPIView
from activemembers.api.permissions import NextCloudPermission
from activemembers.api.serializers import (
NextCloudMemberSerializer,
NextCloudGroupSerializer
NextCloudGroupSerializer,
)
from activemembers.models import MemberGroupMembership, MemberGroup, Board
from members.models import Member
......@@ -21,70 +21,79 @@ class NextCloudUsersView(ListAPIView):
serializer_class = NextCloudMemberSerializer
def get_queryset(self):
perm = Permission.objects.get(content_type__app_label='members',
codename='nextcloud_admin')
return super().get_queryset().filter(
Q(pk__in=MemberGroupMembership.active_objects.values_list(
'member_id', flat=True)) |
Q(is_superuser=True) |
Q(groups__permissions=perm) |
Q(user_permissions=perm) |
(Q(membergroup__permissions=perm) &
(Q(membergroupmembership__until=None) |
Q(membergroupmembership__until__gte=timezone.now())))
).distinct()
perm = Permission.objects.get(
content_type__app_label="members", codename="nextcloud_admin"
)
return (
super()
.get_queryset()
.filter(
Q(
pk__in=MemberGroupMembership.active_objects.values_list(
"member_id", flat=True
)
)
| Q(is_superuser=True)
| Q(groups__permissions=perm)
| Q(user_permissions=perm)
| (
Q(membergroup__permissions=perm)
& (
Q(membergroupmembership__until=None)
| Q(membergroupmembership__until__gte=timezone.now())
)
)
)
.distinct()
)
class NextCloudGroupsView(ListAPIView):
permission_classes = [NextCloudPermission]
queryset = (MemberGroup.objects
.exclude(name_en='admin')
.exclude(active=False)
.all())
queryset = MemberGroup.objects.exclude(name_en="admin").exclude(active=False).all()
serializer_class = NextCloudGroupSerializer
def list(self, request, *args, **kwargs):
response = super().list(request, *args, **kwargs)
perm = Permission.objects.get(content_type__app_label='members',
codename='nextcloud_admin')
admin_users = Member.current_members.filter(
Q(is_superuser=True) |
Q(groups__permissions=perm) |
Q(user_permissions=perm) |
(Q(membergroup__permissions=perm) &
(Q(membergroupmembership__until=None) |
Q(membergroupmembership__until__gte=timezone.now())))
).distinct().values_list('username', flat=True)
perm = Permission.objects.get(
content_type__app_label="members", codename="nextcloud_admin"
)
admin_users = (
Member.current_members.filter(
Q(is_superuser=True)
| Q(groups__permissions=perm)
| Q(user_permissions=perm)
| (
Q(membergroup__permissions=perm)
& (
Q(membergroupmembership__until=None)
| Q(membergroupmembership__until__gte=timezone.now())
)
)
)
.distinct()
.values_list("username", flat=True)
)
current_year = datetime_to_lectureyear(datetime.date.today())
try:
board = Board.objects.get(
since__year=current_year, until__year=current_year + 1)
board_users = board.members.values_list('username', flat=True)
since__year=current_year, until__year=current_year + 1
)
board_users = board.members.values_list("username", flat=True)
except Board.DoesNotExist:
board_users = []
committee_chair_users = (MemberGroupMembership.active_objects
.filter(group__board=None)
.filter(group__society=None)
.filter(chair=True)
.values_list('member__username', flat=True))
committee_chair_users = (
MemberGroupMembership.active_objects.filter(group__board=None)
.filter(group__society=None)
.filter(chair=True)
.values_list("member__username", flat=True)
)
response.data = list(response.data) + [
{
'pk': -1,
'name': 'admin',
'members': admin_users
},
{
'pk': -2,
'name': 'Board',
'members': board_users
},
{
'pk': -3,
'name': 'Committee Chairs',
'members': committee_chair_users
}
{"pk": -1, "name": "admin", "members": admin_users},
{"pk": -2, "name": "Board", "members": board_users},
{"pk": -3, "name": "Committee Chairs", "members": committee_chair_users},
]
return response
......@@ -5,8 +5,9 @@ from django.utils.translation import gettext_lazy as _
class ActiveMembersConfig(AppConfig):
"""AppConfig for the activemembers package"""
name = 'activemembers'
verbose_name = _('Active members')
name = "activemembers"
verbose_name = _("Active members")
def ready(self):
"""Imports the signals when the app is ready"""
......
......@@ -28,18 +28,22 @@ class MemberGroupBackend(object):
return set()
groups = member.membergroup_set.filter(
Q(membergroupmembership__until=None) |
Q(membergroupmembership__until__gte=timezone.now())
Q(membergroupmembership__until=None)
| Q(membergroupmembership__until__gte=timezone.now())
)
perm_cache_name = '_membergroup_perm_cache'
perm_cache_name = "_membergroup_perm_cache"
if not hasattr(user, perm_cache_name):
perms = (Permission.objects
.filter(membergroup__in=groups)
.values_list('content_type__app_label', 'codename')
.order_by())
setattr(user, perm_cache_name,
set("{}.{}".format(ct, name) for ct, name in perms))
perms = (
Permission.objects.filter(membergroup__in=groups)
.values_list("content_type__app_label", "codename")
.order_by()
)
setattr(
user,
perm_cache_name,
set("{}.{}".format(ct, name) for ct, name in perms),
)
return getattr(user, perm_cache_name)
def get_all_permissions(self, user, obj=None):
......@@ -58,6 +62,6 @@ class MemberGroupBackend(object):
if not user.is_active:
return False
for perm in self.get_all_permissions(user):
if perm[:perm.index('.')] == app_label:
if perm[: perm.index(".")] == app_label:
return True
return False
......@@ -14,16 +14,15 @@ def send_gsuite_welcome_message(member, email, password):
"""
with translation.override(member.profile.language):
email_body = loader.render_to_string(
'activemembers/email/gsuite_info.txt',
"activemembers/email/gsuite_info.txt",
{
'full_name': member.get_full_name(),
'username': email,
'password': password,
'url': settings.BASE_URL
})
member.email_user(
_('Your new G Suite credentials'),
email_body)
"full_name": member.get_full_name(),
"username": email,
"password": password,
"url": settings.BASE_URL,
},
)
member.email_user(_("Your new G Suite credentials"), email_body)
def send_gsuite_suspended_message(member):
......@@ -34,9 +33,7 @@ def send_gsuite_suspended_message(member):
"""
with translation.override(member.profile.language):
email_body = loader.render_to_string(
'activemembers/email/gsuite_suspend.txt',
{
'full_name': member.get_full_name(),
'url': settings.BASE_URL
})
member.email_user(_('G Suite account suspended'), email_body)
"activemembers/email/gsuite_suspend.txt",
{"full_name": member.get_full_name(), "url": settings.BASE_URL},
)
member.email_user(_("G Suite account suspended"), email_body)
......@@ -9,10 +9,9 @@ from members.models import Member
class MemberGroupMembershipForm(forms.ModelForm):
"""Custom form for group memberships that orders the members"""
member = forms.ModelChoiceField(
queryset=Member.objects.order_by('first_name',
'last_name'),
label=_('Member'),
queryset=Member.objects.order_by("first_name", "last_name"), label=_("Member"),
)
class Meta:
......@@ -31,5 +30,6 @@ class MemberGroupForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.fields['permissions'].queryset = (
Permission.objects.select_related('content_type'))
self.fields["permissions"].queryset = Permission.objects.select_related(
"content_type"
)
......@@ -2,9 +2,7 @@ import hashlib
import logging
from base64 import b16encode
from django.utils.translation import (
ugettext_lazy as _, override as lang_override
)
from django.utils.translation import ugettext_lazy as _, override as lang_override
from googleapiclient.errors import HttpError
from members.models import Member
......@@ -26,56 +24,59 @@ class GSuiteUserService:
:return returns a tuple with the password and id of the created user
"""
plain_password = Member.objects.make_random_password(15)
digest_password = hashlib.sha1(plain_password.encode('utf-8')).digest()
digest_password = hashlib.sha1(plain_password.encode("utf-8")).digest()
encoded_password = b16encode(digest_password).decode("utf-8")
try:
response = self.directory_api.users().insert(
body={
'name': {
'familyName': member.last_name,
'givenName': member.first_name
response = (
self.directory_api.users()
.insert(
body={
"name": {
"familyName": member.last_name,
"givenName": member.first_name,
},
"primaryEmail": f"{member.username}@{settings.GSUITE_MEMBERS_DOMAIN}",
"password": encoded_password,
"hashFunction": "SHA-1",
"changePasswordAtNextLogin": "true",
"externalIds": [{"value": f"{member.pk}", "type": "login_id"}],
"includeInGlobalAddressList": "false",
"orgUnitPath": "/",
},
'primaryEmail':
f'{member.username}@{settings.GSUITE_MEMBERS_DOMAIN}',
'password': encoded_password,
'hashFunction': 'SHA-1',
'changePasswordAtNextLogin': 'true',
'externalIds': [{
'value': f'{member.pk}',
'type': 'login_id'
}],
'includeInGlobalAddressList': 'false',
'orgUnitPath': '/',
},
).execute()
)
.execute()
)
except HttpError as e:
if e.resp.status == 409:
return self.update_user(member, member.username)
raise e
return response['primaryEmail'], plain_password
return response["primaryEmail"], plain_password
def update_user(self, member: Member, username: str):
response = self.directory_api.users().patch(
body={
'suspended': 'false',
'primaryEmail':
f'{member.username}@{settings.GSUITE_MEMBERS_DOMAIN}',
},
userKey=f'{username}@{settings.GSUITE_MEMBERS_DOMAIN}'
).execute()
response = (
self.directory_api.users()
.patch(
body={