Skip to content

Commit

Permalink
Merge pull request #478 from MAKENTNU/dev
Browse files Browse the repository at this point in the history
Deployment
  • Loading branch information
ddabble authored Jul 17, 2022
2 parents 009a8e7 + adf40ce commit 9016a87
Show file tree
Hide file tree
Showing 195 changed files with 2,090 additions and 1,911 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ jobs:
- uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3.1.2
uses: actions/setup-python@v4.1.0
with:
python-version: ${{ matrix.python-version }}
cache: "pip"
Expand Down
34 changes: 17 additions & 17 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ coverage.xml
.pytest_cache/
cover/

# Translations
# Don't ignore .mo files, as they contain the compiled translations used by Django
#*.mo

# Django stuff:

# Flask stuff:
Expand Down Expand Up @@ -281,27 +285,22 @@ fabric.properties
.idea/caches/build_file_checksums.ser

### PyCharm+all Patch ###
# Ignores the whole .idea folder and all .iml files
# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
# Ignore everything but code style settings and run configurations
# that are supposed to be shared within teams.

.idea/*

# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023

*.iml
modules.xml
.idea/misc.xml
*.ipr

# Sonarlint plugin
.idea/sonarlint
# Don't include these, as we currently don't share things like this
#!.idea/codeStyles
#!.idea/runConfigurations

### venv ###
# Virtualenv
# http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
[Bb]in
[Ii]nclude
# Can't ignore [Ll]ib, as that would ignore `web/static/lib`
# Don't ignore lib (as that would ignore `web/static/lib`)
#[Ll]ib
[Ll]ib64
[Ll]ocal
[Ss]cripts
Expand All @@ -310,11 +309,12 @@ pip-selfcheck.json

### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets
# Don't include these, as we currently don't share things like this
#!.vscode/settings.json
#!.vscode/tasks.json
#!.vscode/launch.json
#!.vscode/extensions.json
#!.vscode/*.code-snippets

# Local History for Visual Studio Code
.history/
Expand Down
35 changes: 35 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,41 @@ A summary of changes made to the codebase, grouped per deployment.
## Unreleased


## 2022-07-17 ([#478](https://github.com/MAKENTNU/web/pull/478))
### New features
- Added a URL which always links to the current week for a machine reservation calendar
- This URL can be copied by right-clicking the "View in calendar" button for a machine on [the machine list page](https://makentnu.no/reservation/),
and selecting "Copy link address"
- Made machine streams work with the new Raspberry Pi setup
- Also, only the visible streams are connected to;
once the page is scrolled so that a stream image is no longer rendered, the stream for that machine is disconnected,
which makes [the machine list page](https://makentnu.no/reservation/) use considerably less data (depending on how the page is scrolled)
- This does not apply to the machine detail (calendar) page, as there is always only one stream on the page

### Improvements
- Loading the member list has been made slightly faster
- Renamed a lot of templates (and CSS and JavaScript files) to comply with the style guide
- Improved word breaking (splitting a word between two lines, often using a hyphen) multiple places, like in titles and descriptions
- Added translations to the spreadsheet containing course registrations, that can be downloaded from
[the course registrations page](https://makentnu.no/reservation/course/)
- Improved the permission check for [the admin panel](https://makentnu.no/admin/)
- Added edit and delete buttons on the machine detail (calendar) page
- On the history page for a documentation page (on [docs.makentnu.no](https://docs.makentnu.no/)),
the current version link is now a permalink to that specific version

### Fixes
- Fixed endless redirect loop when a logged-in user visited a page that required a permission that the user didn't have
- Fixed error messages for multilingual form fields (like the event "Content" field) not showing
- Previously, editing CKEditor fields (like the event "Content" field) did not prevent the user from leaving the page;
this has now been fixed
- Fixed sometimes being unable to close fullscreen machine streams, when clicking the close ("X") button
- Fixed old page version warning when visiting the URL for a specific documentation page version which is the current version of that page

### Other changes
- Added setup instructions to the project's README file
- Much code cleanup, yes


## 2022-06-07 ([#461](https://github.com/MAKENTNU/web/pull/461))
### New features
- Added table on [the profile page](https://makentnu.no/checkin/profile/) which displays the completion status of all available courses
Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -989,6 +989,7 @@ the `as_view()` call can be wrapped in one of the decorators corresponding to th
(i.e. [`permission_required()`](https://docs.djangoproject.com/en/stable/topics/auth/default/#the-permission-required-decorator),
[`login_required()`](https://docs.djangoproject.com/en/stable/topics/auth/default/#the-login-required-decorator),
and [`user_passes_test()`](https://docs.djangoproject.com/en/stable/topics/auth/default/#django.contrib.auth.decorators.user_passes_test)).
It's generally best to use our custom `permission_required_else_denied()` decorator, unless you're sure of otherwise.

#### Modifying state in `GET` requests
This includes things like modifying objects in the database or files on the server.
Expand Down Expand Up @@ -1266,7 +1267,7 @@ All statements should end with a `;`.

#### Comparing values loosely (with `==`)
The `===` operator should always be favored over `==`. [See some of the reasons in the answers to
this question](https://stackoverflow.com/questions/359494/which-equals-operator-vs-should-be-used-in-javascript-comparisons).
this question](https://stackoverflow.com/q/359494).

Of course, this also applies to `!==` vs. `!=`.

Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2017-2021 MAKE NTNU
Copyright (c) 2017-2022 MAKE NTNU

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,83 @@
[![codecov](https://codecov.io/gh/MAKENTNU/web/branch/main/graph/badge.svg)](https://codecov.io/gh/MAKENTNU/web)


## Setup
<details>
<summary>Click to expand</summary>

### Prerequisites
* Python 3.8+ (latest stable version preferred)
* Having cloned this repository to your machine
* For most purposes, check out [the `dev` branch](https://github.com/MAKENTNU/web/tree/dev), as it's the base branch for all development:
```shell
git clone https://github.com/MAKENTNU/web.git
git checkout -B dev origin/dev
```

### Installation
* Create a virtual environment, presumably named `venv`:
* This should be placed in the folder *containing* the project folder, and not inside the project folder itself
* Example folder structure (where `web` is the name of the project folder):
```
MAKE
├─ venv
└─ web
└─ README.md (this file)
```
* Among other things, this prevents translations from being made inside the virtual environment folder
when running the `makemessages` management command
* If using PyCharm, and a virtual environment was not created as part of the project creation process, refer to
[the "Configure a virtual environment" guide](https://www.jetbrains.com/help/pycharm/creating-virtual-environment.html#python_create_virtual_env)
* Otherwise, `cd` to the project folder, and run:
```shell
[newest installed Python command, like python3.10] -m venv ../venv
```
* Activate the virtual environment:
* If using PyCharm, this should be done automatically when opening a terminal tab inside the IDE
* Otherwise, `cd` to the project folder, and run:
* On Windows:
```shell
..\venv\Scripts\activate
```
* On Linux/macOS:
```shell
source ../venv/bin/activate
```
* Install requirements:
* If using Windows, first download the correct wheel for the [`python-ldap`](https://pypi.org/project/python-ldap/) package
from [Christoph Gohlke's page](https://www.lfd.uci.edu/~gohlke/pythonlibs/#_python-ldap)
(linked to by [`python-ldap`'s documentation](https://www.python-ldap.org/en/python-ldap-3.4.0/installing.html#windows))
and install it:
```shell
pip install [path to .whl file]
```
(It is possible to instead build `python-ldap` from source, but it's a bit cumbersome setting up the right build tools.)
* Regardless of operating system, run:
```shell
pip install -r requirements.txt
```
### Running the server for the first time
* Create an SQLite database file with the proper tables:
```shell
python manage.py migrate
```
* Create an admin user for local development:
```shell
python manage.py createsuperuser
```
It's easiest to create one with both the username and the password set to "admin", and with no email address.
* Run the server:
* If using PyCharm, just press the green "play" button in the top right corner
* Make sure that the correct run configuration is selected in the dropdown next to the button,
which by default should be named "web" and have a tiny Django logo
* Otherwise, run:
```shell
python manage.py runserver [optional port number; defaults to 8000]
```
</details>


## Contribution guidelines
See [CONTRIBUTING.md](CONTRIBUTING.md) for the following topics:
* [Code style guides](CONTRIBUTING.md#code-style-guides)
Expand Down
8 changes: 4 additions & 4 deletions announcements/forms.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
class AnnouncementForm(ModelForm):
class Meta:
model = Announcement
fields = "__all__"
fields = '__all__'
widgets = {
"classification": SemanticChoiceInput(),
"display_from": SemanticDateTimeInput(attrs={"end_calendar": "display_to"}),
"display_to": SemanticDateTimeInput(attrs={"start_calendar": "display_from", "default_blank": True}),
'classification': SemanticChoiceInput(),
'display_from': SemanticDateTimeInput(end_calendar_name='display_to'),
'display_to': SemanticDateTimeInput(start_calendar_name='display_from', default_blank=True),
}
8 changes: 4 additions & 4 deletions announcements/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,27 +9,27 @@

class AnnouncementQuerySet(models.QuerySet):

def shown(self):
def shown(self) -> 'AnnouncementQuerySet[Announcement]':
"""Returns a ``QuerySet`` with only the announcements that are currently shown."""
now = timezone.localtime()
return self.filter(
Q(display_from__lte=now)
& (Q(display_to__isnull=True) | Q(display_to__gt=now))
)

def not_shown(self):
def not_shown(self) -> 'AnnouncementQuerySet[Announcement]':
"""Returns a ``QuerySet`` with only the announcements that are currently not shown."""
now = timezone.localtime()
return self.filter(
Q(display_from__lte=now) & Q(display_to__lte=now)
| Q(display_from__gt=now)
)

def site_wide(self):
def site_wide(self) -> 'AnnouncementQuerySet[Announcement]':
"""Returns a ``QuerySet`` with only the announcements that should be displayed site-wide."""
return self.filter(site_wide=True)

def non_site_wide(self):
def non_site_wide(self) -> 'AnnouncementQuerySet[Announcement]':
"""Returns a ``QuerySet`` with only the announcements that should not be displayed site-wide."""
return self.filter(site_wide=False)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ <h1>
<i class="make-col-yellow home icon"></i>{% trans "Front page" context "location" %}
{% endif %}
</div>
<div class="description">{{ announcement.content }}</div>
<div class="word-breaking description">{{ announcement.content }}</div>
<div class="extra">
<span class="ui {% if announcement.is_shown %}green{% else %}red{% endif %} text">
{% if announcement.display_to %}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
{% load announcement_tags %}


<div class="{% announcement_css_class announcement %} announcement">
{# Linking `announcement_banner.css` is required when including this template #}

<div class="word-breaking {% announcement_css_class announcement %} announcement">
{% if announcement.link %}
<a href="{{ announcement.link }}" target="_blank">
{{ announcement.content }}
Expand Down
2 changes: 1 addition & 1 deletion announcements/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class AnnouncementAdminView(PermissionRequiredMixin, ListView):
permission_required = ('announcements.change_announcement',)
model = Announcement
queryset = Announcement.objects.order_by('-display_from')
template_name = 'announcements/announcement_admin.html'
template_name = 'announcements/admin_announcement_list.html'
context_object_name = "announcements"


Expand Down
16 changes: 9 additions & 7 deletions card/modelfields.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ class CardNumberField(models.CharField):
"""
Custom field for card numbers, doing some extra validation.
"""
empty_strings_allowed = False # empty values should be stored as None
default_validators = [card_number_validator]

def __init__(self, **kwargs):
super().__init__(**{
'verbose_name': _("card number"),
'max_length': 10, # No card numbers are more than ten digits long
'error_messages': {
"unique": _("Card number already in use"),
'unique': _("Card number already in use"),
},
**kwargs,
})
Expand All @@ -30,13 +31,14 @@ def formfield(self, **kwargs):

def get_prep_value(self, value):
if isinstance(value, CardNumber):
return value.number
return str(value.number)
elif isinstance(value, str):
# Only try to remove the any EM prefix if the string is just whitespace
if value.strip():
value = value.strip()
# Only try to remove an EM prefix if the string is not just whitespace
if value:
return value.split()[-1] # Remove possible EM prefix
return "" # No need to include the whitespace
return value
# `value` is either None or not an acceptable value
return None

def from_db_value(self, value, expression, connection):
if value:
Expand All @@ -50,7 +52,7 @@ class CardNumber:
"""

def __init__(self, number):
self.number = number
self.number = str(number)

def __str__(self):
return f"EM {self.number}"
26 changes: 13 additions & 13 deletions card/tests/test_modelfields.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from django.test import TestCase
from unittest import TestCase

from ..modelfields import CardNumber, CardNumberField

Expand All @@ -7,22 +7,22 @@ class CardNumberModelFieldTest(TestCase):

def test_get_prep_value(self):
field = CardNumberField()
self.assertEqual("", field.get_prep_value(""), "The prep value of an empty string should be an empty string")
self.assertEqual("", field.get_prep_value(" "),
"The prep value of a string of space should be an empty string")
self.assertEqual("", field.get_prep_value("\t\n \t\n "),
"The prep value of a string of whitespace should be an empty string")
self.assertEqual("1234567890", field.get_prep_value("1234567890"),
self.assertIs(field.get_prep_value(""), None, "The prep value of an empty string should be None")
self.assertIs(field.get_prep_value(" "), None,
"The prep value of a string of space should be None")
self.assertIs(field.get_prep_value("\t\n \t\n "), None,
"The prep value of a string of whitespace should be None")
self.assertEqual(field.get_prep_value("1234567890"), "1234567890",
"The prep value of a string of digits should be the same string")
self.assertEqual("1234567890", field.get_prep_value("EM 1234567890"),
self.assertEqual(field.get_prep_value("EM 1234567890"), "1234567890",
"The prep value of an EM number prefixed by EM should be the EM number without the prefix")
self.assertEqual("1234567890", field.get_prep_value(CardNumber("1234567890")),
self.assertEqual(field.get_prep_value(CardNumber("1234567890")), "1234567890",
"The prep value of a CardNumber object should be its value")

def test_from_db_value(self):
field = CardNumberField()
value = field.from_db_value("1234567890", "", "")
self.assertEqual(CardNumber, type(value), "If the DB value is not empty it should be a CardNumber object")
self.assertEqual("1234567890", value.number, "The CardNumber objects should have the EM number as its number")
value = field.from_db_value("1234567890", None, None)
self.assertIs(type(value), CardNumber, "If the DB value is not empty it should be a CardNumber object")
self.assertEqual(value.number, "1234567890", "The CardNumber objects should have the EM number as its number")

self.assertEqual(None, field.from_db_value("", "", ""), "An empty DB value should return None")
self.assertEqual(field.from_db_value("", None, None), None, "An empty DB value should return None")
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@
width: 150px;
height: 150px;
border-radius: 10px;
}

#profile-pic:hover {
cursor: pointer;
}

Expand Down
File renamed without changes.
Loading

0 comments on commit 9016a87

Please sign in to comment.