mirror of https://github.com/ae-utbm/sith3.git
Compare commits
No commits in common. "3501703c15ac6c2641c734b357482591e8dfc3e1" and "8129e6923ad6decebf58622090e146ff5b942aeb" have entirely different histories.
3501703c15
...
8129e6923a
|
@ -13,4 +13,3 @@ sith/settings_custom.py
|
|||
sith/search_indexes/
|
||||
.coverage
|
||||
coverage_report/
|
||||
doc/_build
|
||||
|
|
|
@ -5,7 +5,7 @@ test:
|
|||
stage: test
|
||||
script:
|
||||
- apt-get update
|
||||
- apt-get install -y gettext python3-xapian libgraphviz-dev
|
||||
- apt-get install -y gettext python3-xapian
|
||||
- pushd /usr/lib/python3/dist-packages/xapian && ln -s _xapian* _xapian.so && popd
|
||||
- export PYTHONPATH="/usr/lib/python3/dist-packages:$PYTHONPATH"
|
||||
- python -c 'import xapian' # Fail immediately if there is a problem with xapian
|
||||
|
@ -15,8 +15,6 @@ test:
|
|||
- coverage run ./manage.py test
|
||||
- coverage html
|
||||
- coverage report
|
||||
- cd doc
|
||||
- make html # Make documentation
|
||||
artifacts:
|
||||
paths:
|
||||
- coverage_report/
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
# Read the Docs configuration file
|
||||
# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
|
||||
|
||||
# Required
|
||||
version: 2
|
||||
|
||||
# Build documentation in the doc/ directory with Sphinx
|
||||
sphinx:
|
||||
configuration: doc/conf.py
|
||||
|
||||
# Optionally build your docs in additional formats such as PDF and ePub
|
||||
formats: all
|
||||
|
||||
# Optionally set the version of Python and requirements required to build your docs
|
||||
python:
|
||||
version: 3.6
|
||||
install:
|
||||
- requirements: requirements.txt
|
|
@ -0,0 +1,106 @@
|
|||
*Contribuer c'est la vie*
|
||||
=========================
|
||||
|
||||
Hey ! Tu veux devenir un mec bien et en plus devenir bon en python si tu l'es pas déjà ?
|
||||
Il se trouve que le sith AE prévu pour l'été 2016 a besoin de toi !
|
||||
|
||||
Pour faire le sith, on utilise le framework Web [Django](https://docs.djangoproject.com/fr/1.11/intro/)
|
||||
N'hésite pas à lire les tutos et à nous demander (ae.info@utbm.fr).
|
||||
|
||||
Bon, passons aux choses sérieuses, pour bidouiller le sith sans le casser :
|
||||
Ben en fait, tu peux pas le casser, tu vas juste t'amuser comme un petit fou sur un clone du sith.
|
||||
|
||||
C'est pas compliqué, il suffit d'avoir [Git](http://www.git-scm.com/book/fr/v2), python et pip (pour faciliter la gestion des paquets python).
|
||||
|
||||
Tout d'abord, tu vas avoir besoin d'un compte Gitlab pour pouvoir te connecter.
|
||||
Ensuite, tu fais :
|
||||
`git clone https://ae-dev.utbm.fr/ae/Sith.git`
|
||||
Avec cette commande, tu clones le sith AE dans le dossier courant.
|
||||
|
||||
```bash
|
||||
cd Sith
|
||||
virtualenv --system-site-packages --python=python3 env
|
||||
source env/bin/activate
|
||||
pip install -r requirements.txt
|
||||
./manage runserver
|
||||
```
|
||||
|
||||
Attention aux dépendances système, à voir dans le README.md
|
||||
|
||||
Maintenant, faut passer le sith en mode debug dans le fichier de settings personnalisé.
|
||||
|
||||
```bash
|
||||
echo "DEBUG=True" > sith/settings_custom.py
|
||||
echo 'SITH_URL = "localhost:8000"' >> sith/settings_custom.py
|
||||
```
|
||||
|
||||
Enfin, il s'agit de créer la base de donnée de test lors de la première utilisation
|
||||
|
||||
```bash
|
||||
./manage.py setup
|
||||
```
|
||||
|
||||
Et pour lancer le sith, tu fais `python3 manage.py runserver`
|
||||
|
||||
Voilà, c'est le sith AE. Il y a des issues dans le gitlab qui sont à régler. Si tu as un domaine qui t'intéresse, une appli que tu voudrais développer, n'hésites pas et contacte-nous.
|
||||
Va, et que l'AE soit avec toi.
|
||||
|
||||
# Black
|
||||
|
||||
Pour uniformiser le formattage du code nous utilisons [Black](https://github.com/ambv/black). Cela permet d'avoir le même codestyle et donc le codereview prend moins de temps. Tout étant dans le même format, il est plus facile pour chacun de comprendre le code de chacun ! Cela permet aussi d'éviter des erreurs (y parait 🤷♀️).
|
||||
|
||||
Installation de black:
|
||||
|
||||
```bash
|
||||
pip install black
|
||||
```
|
||||
|
||||
## Sous VsCode:
|
||||
Attention, pour VsCode, Black doit être installé dans votre virtualenv !
|
||||
Ajouter ces deux lignes dans les settings de VsCode
|
||||
|
||||
```json
|
||||
{
|
||||
"python.formatting.provider": "black",
|
||||
"editor.formatOnSave": true
|
||||
}
|
||||
```
|
||||
|
||||
## Sous Sublime Text
|
||||
Il faut installer le plugin [sublack](https://packagecontrol.io/packages/sublack) depuis Package Control.
|
||||
|
||||
Il suffit ensuite d'ajouter dans les settings du projet (ou en global)
|
||||
|
||||
```json
|
||||
{
|
||||
"sublack.black_on_save": true
|
||||
}
|
||||
```
|
||||
|
||||
Si vous utilisez le plugin [anaconda](http://damnwidget.github.io/anaconda/), pensez à modifier les paramètres du linter pep8 pour éviter de recevoir des warnings dans le formatage de black
|
||||
|
||||
```json
|
||||
{
|
||||
"pep8_ignore": [
|
||||
"E203",
|
||||
"E266",
|
||||
"E501",
|
||||
"W503"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Sites et doc cools
|
||||
------------------
|
||||
|
||||
[Classy Class-Based Views](http://ccbv.co.uk/projects/Django/1.11/)
|
||||
|
||||
Helpers:
|
||||
|
||||
`./manage.py makemessages --ignore "env/*" -e py,jinja`
|
||||
|
||||
`for f in $(find . -name "*.py" ! -path "*migration*" ! -path "./env/*" ! -path "./doc/*"); do cat ./doc/header "$f" > /tmp/temp && mv /tmp/temp "$f"; done`
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
Pour contribuer au projet, vous pouvez vous référer à la documentation disponible à https://sith-ae.readthedocs.io/.
|
||||
|
||||
Et n'oubliez pas, contribuer c'est la vie !
|
|
@ -0,0 +1,104 @@
|
|||
[![pipeline status](https://ae-dev.utbm.fr/ae/Sith/badges/master/pipeline.svg)](https://ae-dev.utbm.fr/ae/Sith/commits/master)
|
||||
[![coverage report](https://ae-dev.utbm.fr/ae/Sith/badges/master/coverage.svg)](https://ae-dev.utbm.fr/ae/Sith/commits/master)
|
||||
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/ambv/black)
|
||||
[![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://ae-dev.zulipchat.com)
|
||||
|
||||
## Sith AE
|
||||
|
||||
### Dependencies:
|
||||
See requirements.txt
|
||||
|
||||
You may need to install some dev libraries like `libmysqlclient-dev`, `libssl-dev`, `libjpeg-dev`, `python3-xapian`, or `zlib1g-dev` to install all the
|
||||
requiered dependancies with pip. You may also need `mysql-client`. Don't also forget `python3-dev` if you don't have it
|
||||
already.
|
||||
|
||||
You can check all of them with:
|
||||
|
||||
```bash
|
||||
sudo apt install libmysqlclient-dev libssl-dev libjpeg-dev zlib1g-dev python3-dev libffi-dev python3-dev libgraphviz-dev pkg-config python3-xapian gettext
|
||||
```
|
||||
|
||||
On macos, you will need homebrew
|
||||
|
||||
```bash
|
||||
brew install xapian
|
||||
```
|
||||
|
||||
If it doesn't work it's because it need [this pull request](https://github.com/Homebrew/homebrew-core/pull/34835) to be validated.
|
||||
|
||||
The development is done with sqlite, but it is advised to set a more robust DBMS for production (Postgresql for example)
|
||||
|
||||
### Get started
|
||||
|
||||
To start working on the project, just run the following commands:
|
||||
|
||||
```bash
|
||||
git clone https://ae-dev.utbm.fr/ae/Sith.git
|
||||
cd Sith
|
||||
virtualenv --system-site-packages --python=python3 env
|
||||
source env/bin/activate
|
||||
pip install -r requirements.txt
|
||||
./manage.py setup
|
||||
```
|
||||
|
||||
To start the simple development server, just run `python3 manage.py runserver`
|
||||
|
||||
For more informations, check out the CONTRIBUTING.md file.
|
||||
|
||||
### Logging errors with sentry
|
||||
|
||||
To connect the app to sentry.io, you must set the variable SENTRY_DSN in your settings custom. It's composed of the full link given on your sentry project
|
||||
|
||||
### Generating documentation
|
||||
|
||||
There is a Doxyfile at the root of the project, meaning that if you have Doxygen, you can run `doxygen Doxyfile` to
|
||||
generate a complete HTML documentation that will be available in the *./doc/html/* folder.
|
||||
|
||||
### Collecting statics for production:
|
||||
|
||||
We use scss in the project. In development environment (DEBUG=True), scss is compiled every time the file is needed. For production, it assumes you have already compiled every files and to do so, you need to use the following commands :
|
||||
|
||||
```bash
|
||||
./manage.py collectstatic # To collect statics
|
||||
./manage.py compilestatic # To compile scss in those statics
|
||||
```
|
||||
|
||||
### Misc about development
|
||||
|
||||
#### Controlling the rights
|
||||
|
||||
When you need to protect an object, there are three levels:
|
||||
* Editing the object properties
|
||||
* Editing the object various values
|
||||
* Viewing the object
|
||||
|
||||
Now you have many solutions in your model:
|
||||
* You can define a `is_owned_by(self, user)`, a `can_be_edited_by(self, user)`, and/or a `can_be_viewed_by(self, user)`
|
||||
method, each returning True is the user passed can edit/view the object, False otherwise.
|
||||
This allows you to make complex request when the group solution is not powerful enough.
|
||||
It's useful too when you want to define class-wide permissions, e.g. the club members, that are viewable only for
|
||||
Subscribers.
|
||||
* You can add an `owner_group` field, as a ForeignKey to Group. Second is an `edit_groups` field, as a ManyToMany to
|
||||
Group, and third is a `view_groups`, same as for edit.
|
||||
|
||||
Finally, when building a class based view, which is highly advised, you just have to inherit it from CanEditPropMixin,
|
||||
CanEditMixin, or CanViewMixin, which are located in core.views. Your view will then be protected using either the
|
||||
appropriate group fields, or the right method to check user permissions.
|
||||
|
||||
#### Counting the number of line of code
|
||||
|
||||
```bash
|
||||
sudo apt install cloc
|
||||
cloc --exclude-dir=doc,env .
|
||||
```
|
||||
|
||||
#### Updating doc/SYNTAX.md
|
||||
|
||||
If you make an update in the Markdown syntax parser, it's good to document
|
||||
update the syntax reference page in `doc/SYNTAX.md`. But updating this file will
|
||||
break the tests if you don't update the corresponding `doc/SYNTAX.html` file at
|
||||
the same time.
|
||||
To do that, simply run `./manage.py markdown > doc/SYNTAX.html`,
|
||||
and the tests should pass again.
|
||||
|
||||
|
37
README.rst
37
README.rst
|
@ -1,37 +0,0 @@
|
|||
.. image:: https://ae-dev.utbm.fr/ae/Sith/badges/master/pipeline.svg
|
||||
:target: https://ae-dev.utbm.fr/ae/Sith/commits/master
|
||||
:alt: pipeline status
|
||||
|
||||
.. image:: https://readthedocs.org/projects/sith-ae/badge/?version=latest
|
||||
:target: https://sith-ae.readthedocs.io/?badge=latest
|
||||
:alt: documentation Status
|
||||
|
||||
.. image:: https://ae-dev.utbm.fr/ae/Sith/badges/master/coverage.svg
|
||||
:target: https://ae-dev.utbm.fr/ae/Sith/commits/master
|
||||
:alt: coverage report
|
||||
|
||||
.. image:: https://img.shields.io/badge/code%20style-black-000000.svg
|
||||
:target: https://github.com/ambv/black
|
||||
:alt: code style: black
|
||||
|
||||
.. image:: https://img.shields.io/badge/zulip-join_chat-brightgreen.svg
|
||||
:target: https://ae-dev.zulipchat.com
|
||||
:alt: project chat
|
||||
|
||||
This is the source code of the UTBM's student association available at https://ae.utbm.fr/.
|
||||
|
||||
All documentation is in the ``docs`` directory and online at https://sith-ae.readthedocs.io/. This documentation is written in French because it targets a French audience and it's too much work to maintain two versions. The code and code comments are strictly written in English.
|
||||
|
||||
If you want to contribute, here's how we recommend to read the docs:
|
||||
|
||||
* First, it's advised to read the about part of the project to understand the goals and the mindset of the current and previous maintainers and know what to expect to learn.
|
||||
* If in the first part you find you need more background about what we use, we provide some links to tutorials and documentation at the end of our documentation. Feel free to use it and complete it with what you found helpful.
|
||||
* Keep in mind that this documentation is thought to be read in order.
|
||||
|
||||
To join our team :
|
||||
|
||||
* Send a mail at mailto:ae.utbm.fr
|
||||
* Join our group chat at https://ae-dev.zulipchat.com
|
||||
* See and join our Trello at https://trello.com/b/YQOaF33m/site-ae.
|
||||
|
||||
This project is licenced under GNU GPL, see the LICENSE file at the top of the repository for more details.
|
|
@ -0,0 +1,9 @@
|
|||
# TODO
|
||||
|
||||
## Easter eggs
|
||||
|
||||
* 'A' 'L' 'L' 'O': Entendre le Allooo de Madame Coucoune
|
||||
* idem avec cacafe
|
||||
* Un meat spin quelque part
|
||||
* Konami code
|
||||
|
|
@ -4,7 +4,6 @@ from __future__ import unicode_literals
|
|||
from django.db import migrations, models
|
||||
import django.core.validators
|
||||
import accounting.models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -244,7 +243,6 @@ class Migration(migrations.Migration):
|
|||
verbose_name="accounting type",
|
||||
to="accounting.AccountingType",
|
||||
blank=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -269,7 +267,6 @@ class Migration(migrations.Migration):
|
|||
verbose_name="simplified accounting types",
|
||||
to="accounting.AccountingType",
|
||||
related_name="simplified_types",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -23,7 +22,6 @@ class Migration(migrations.Migration):
|
|||
verbose_name="invoice",
|
||||
to="core.SithFile",
|
||||
blank=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
|
@ -33,14 +31,12 @@ class Migration(migrations.Migration):
|
|||
verbose_name="journal",
|
||||
to="accounting.GeneralJournal",
|
||||
related_name="operations",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="operation",
|
||||
name="linked_operation",
|
||||
field=models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
blank=True,
|
||||
to="accounting.Operation",
|
||||
null=True,
|
||||
|
@ -58,7 +54,6 @@ class Migration(migrations.Migration):
|
|||
verbose_name="simple type",
|
||||
to="accounting.SimplifiedAccountingType",
|
||||
blank=True,
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
|
@ -68,7 +63,6 @@ class Migration(migrations.Migration):
|
|||
verbose_name="club account",
|
||||
to="accounting.ClubAccount",
|
||||
related_name="journals",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
|
@ -78,27 +72,20 @@ class Migration(migrations.Migration):
|
|||
verbose_name="bank account",
|
||||
to="accounting.BankAccount",
|
||||
related_name="club_accounts",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="clubaccount",
|
||||
name="club",
|
||||
field=models.ForeignKey(
|
||||
verbose_name="club",
|
||||
to="club.Club",
|
||||
related_name="club_account",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
verbose_name="club", to="club.Club", related_name="club_account"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="bankaccount",
|
||||
name="club",
|
||||
field=models.ForeignKey(
|
||||
verbose_name="club",
|
||||
to="club.Club",
|
||||
related_name="bank_accounts",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
verbose_name="club", to="club.Club", related_name="bank_accounts"
|
||||
),
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
|
|
|
@ -29,7 +29,6 @@ class Migration(migrations.Migration):
|
|||
related_name="labels",
|
||||
verbose_name="club account",
|
||||
to="accounting.ClubAccount",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -22,7 +22,7 @@
|
|||
#
|
||||
#
|
||||
|
||||
from django.urls import reverse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core import validators
|
||||
from django.db import models
|
||||
|
@ -110,12 +110,7 @@ class BankAccount(models.Model):
|
|||
name = models.CharField(_("name"), max_length=30)
|
||||
iban = models.CharField(_("iban"), max_length=255, blank=True)
|
||||
number = models.CharField(_("account number"), max_length=255, blank=True)
|
||||
club = models.ForeignKey(
|
||||
Club,
|
||||
related_name="bank_accounts",
|
||||
verbose_name=_("club"),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
club = models.ForeignKey(Club, related_name="bank_accounts", verbose_name=_("club"))
|
||||
|
||||
class Meta:
|
||||
verbose_name = _("Bank account")
|
||||
|
@ -141,17 +136,9 @@ class BankAccount(models.Model):
|
|||
|
||||
class ClubAccount(models.Model):
|
||||
name = models.CharField(_("name"), max_length=30)
|
||||
club = models.ForeignKey(
|
||||
Club,
|
||||
related_name="club_account",
|
||||
verbose_name=_("club"),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
club = models.ForeignKey(Club, related_name="club_account", verbose_name=_("club"))
|
||||
bank_account = models.ForeignKey(
|
||||
BankAccount,
|
||||
related_name="club_accounts",
|
||||
verbose_name=_("bank account"),
|
||||
on_delete=models.CASCADE,
|
||||
BankAccount, related_name="club_accounts", verbose_name=_("bank account")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -216,11 +203,7 @@ class GeneralJournal(models.Model):
|
|||
name = models.CharField(_("name"), max_length=40)
|
||||
closed = models.BooleanField(_("is closed"), default=False)
|
||||
club_account = models.ForeignKey(
|
||||
ClubAccount,
|
||||
related_name="journals",
|
||||
null=False,
|
||||
verbose_name=_("club account"),
|
||||
on_delete=models.CASCADE,
|
||||
ClubAccount, related_name="journals", null=False, verbose_name=_("club account")
|
||||
)
|
||||
amount = CurrencyField(_("amount"), default=0)
|
||||
effective_amount = CurrencyField(_("effective_amount"), default=0)
|
||||
|
@ -280,11 +263,7 @@ class Operation(models.Model):
|
|||
|
||||
number = models.IntegerField(_("number"))
|
||||
journal = models.ForeignKey(
|
||||
GeneralJournal,
|
||||
related_name="operations",
|
||||
null=False,
|
||||
verbose_name=_("journal"),
|
||||
on_delete=models.CASCADE,
|
||||
GeneralJournal, related_name="operations", null=False, verbose_name=_("journal")
|
||||
)
|
||||
amount = CurrencyField(_("amount"))
|
||||
date = models.DateField(_("date"))
|
||||
|
@ -303,7 +282,6 @@ class Operation(models.Model):
|
|||
verbose_name=_("invoice"),
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
done = models.BooleanField(_("is done"), default=False)
|
||||
simpleaccounting_type = models.ForeignKey(
|
||||
|
@ -312,7 +290,6 @@ class Operation(models.Model):
|
|||
verbose_name=_("simple type"),
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
accounting_type = models.ForeignKey(
|
||||
"AccountingType",
|
||||
|
@ -320,7 +297,6 @@ class Operation(models.Model):
|
|||
verbose_name=_("accounting type"),
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
label = models.ForeignKey(
|
||||
"Label",
|
||||
|
@ -352,7 +328,6 @@ class Operation(models.Model):
|
|||
null=True,
|
||||
blank=True,
|
||||
default=None,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -512,7 +487,6 @@ class SimplifiedAccountingType(models.Model):
|
|||
AccountingType,
|
||||
related_name="simplified_types",
|
||||
verbose_name=_("simplified accounting types"),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
@ -544,10 +518,7 @@ class Label(models.Model):
|
|||
|
||||
name = models.CharField(_("label"), max_length=64)
|
||||
club_account = models.ForeignKey(
|
||||
ClubAccount,
|
||||
related_name="labels",
|
||||
verbose_name=_("club account"),
|
||||
on_delete=models.CASCADE,
|
||||
ClubAccount, related_name="labels", verbose_name=_("club account")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
|
|
|
@ -20,14 +20,14 @@
|
|||
{% for k,v in statement.items() %}
|
||||
<tr>
|
||||
<td>{{ k }}</td>
|
||||
<td>{{ "%.2f" % v }}</td>
|
||||
<td>{{ v }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
<p><strong>{% trans %}Amount: {% endtrans %}</strong>{{ "%.2f" % object.amount }} €</p>
|
||||
<p><strong>{% trans %}Effective amount: {% endtrans %}</strong>{{ "%.2f" %object.effective_amount }} €</p>
|
||||
<p><strong>{% trans %}Amount: {% endtrans %}</strong>{{ object.amount }} €</p>
|
||||
<p><strong>{% trans %}Effective amount: {% endtrans %}</strong>{{ object.effective_amount }} €</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -18,12 +18,12 @@
|
|||
{% for k,v in dict['CREDIT'].items() %}
|
||||
<tr>
|
||||
<td>{{ k }}</td>
|
||||
<td>{{ "%.2f" % v }}</td>
|
||||
<td>{{ v }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% trans %}Total: {% endtrans %}{{ "%.2f" % dict['CREDIT_sum'] }}
|
||||
{% trans %}Total: {% endtrans %}{{ dict['CREDIT_sum'] }}
|
||||
|
||||
<h6>{% trans %}Debit{% endtrans %}</h6>
|
||||
<table>
|
||||
|
@ -37,19 +37,19 @@
|
|||
{% for k,v in dict['DEBIT'].items() %}
|
||||
<tr>
|
||||
<td>{{ k }}</td>
|
||||
<td>{{ "%.2f" % v }}</td>
|
||||
<td>{{ v }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% trans %}Total: {% endtrans %}{{ "%.2f" % dict['DEBIT_sum'] }}
|
||||
{% trans %}Total: {% endtrans %}{{ dict['DEBIT_sum'] }}
|
||||
{% endmacro %}
|
||||
|
||||
{% block content %}
|
||||
<h3>{% trans %}Statement by nature: {% endtrans %} {{ object.name }}</h3>
|
||||
|
||||
{% for k,v in statement.items() %}
|
||||
<h4 style="background: lightblue; padding: 4px;">{{ k }} : {{ "%.2f" % (v['CREDIT_sum'] - v['DEBIT_sum']) }}</h4>
|
||||
<h4 style="background: lightblue; padding: 4px;">{{ k }} : {{ v['CREDIT_sum'] - v['DEBIT_sum'] }}</h4>
|
||||
{{ display_tables(v) }}
|
||||
<hr>
|
||||
{% endfor %}
|
||||
|
|
|
@ -28,14 +28,14 @@
|
|||
{% else %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
<td>{{ "%.2f" % credit_statement[key] }}</td>
|
||||
<td>{{ credit_statement[key] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
<p>Total : {{ "%.2f" % total_credit }}</p>
|
||||
<p>Total : {{ total_credit }}</p>
|
||||
|
||||
<h4>{% trans %}Debit{% endtrans %}</h4>
|
||||
|
||||
|
@ -56,13 +56,13 @@
|
|||
{% else %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
<td>{{ "%.2f" % debit_statement[key] }}</td>
|
||||
<td>{{ debit_statement[key] }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
<p>Total : {{ "%.2f" % total_debit }}</p>
|
||||
<p>Total : {{ total_debit }}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#
|
||||
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.management import call_command
|
||||
from datetime import date
|
||||
|
||||
|
@ -272,50 +272,30 @@ class OperationTest(TestCase):
|
|||
|
||||
def test_nature_statement(self):
|
||||
self.client.login(username="comptable", password="plop")
|
||||
response = self.client.get(
|
||||
response_get = self.client.get(
|
||||
reverse("accounting:journal_nature_statement", args=[self.journal.id])
|
||||
)
|
||||
self.assertContains(response, "bob (Troll Penché) : 3.00", status_code=200)
|
||||
self.assertTrue(
|
||||
"bob (Troll Pench\\xc3\\xa9) : 3.00" in str(response_get.content)
|
||||
)
|
||||
|
||||
def test_person_statement(self):
|
||||
self.client.login(username="comptable", password="plop")
|
||||
response = self.client.get(
|
||||
response_get = self.client.get(
|
||||
reverse("accounting:journal_person_statement", args=[self.journal.id])
|
||||
)
|
||||
self.assertContains(response, "Total : 5575.72", status_code=200)
|
||||
self.assertContains(response, "Total : 71.42")
|
||||
self.assertContains(
|
||||
response,
|
||||
"""
|
||||
<td><a href="/user/1/">S' Kia</a></td>
|
||||
|
||||
<td>3.00</td>""",
|
||||
)
|
||||
self.assertContains(
|
||||
response,
|
||||
"""
|
||||
<td><a href="/user/1/">S' Kia</a></td>
|
||||
|
||||
<td>823.00</td>""",
|
||||
self.assertTrue(
|
||||
"<td>3.00</td>" in str(response_get.content)
|
||||
and '<td><a href="/user/1/">S' Kia</a></td>'
|
||||
in str(response_get.content)
|
||||
)
|
||||
|
||||
def test_accounting_statement(self):
|
||||
self.client.login(username="comptable", password="plop")
|
||||
response = self.client.get(
|
||||
response_get = self.client.get(
|
||||
reverse("accounting:journal_accounting_statement", args=[self.journal.id])
|
||||
)
|
||||
self.assertContains(
|
||||
response,
|
||||
"""
|
||||
<tr>
|
||||
<td>443 - Crédit - Ce code n'existe pas</td>
|
||||
<td>3.00</td>
|
||||
</tr>""",
|
||||
status_code=200,
|
||||
)
|
||||
self.assertContains(
|
||||
response,
|
||||
"""
|
||||
<p><strong>Montant : </strong>-5504.30 €</p>
|
||||
<p><strong>Montant effectif: </strong>-5504.30 €</p>""",
|
||||
self.assertTrue(
|
||||
"<td>443 - Cr\\xc3\\xa9dit - Ce code n'existe pas</td>"
|
||||
in str(response_get.content)
|
||||
)
|
||||
|
|
|
@ -22,133 +22,131 @@
|
|||
#
|
||||
#
|
||||
|
||||
from django.urls import re_path
|
||||
from django.conf.urls import url
|
||||
|
||||
from accounting.views import *
|
||||
|
||||
urlpatterns = [
|
||||
# Accounting types
|
||||
re_path(
|
||||
url(
|
||||
r"^simple_type$",
|
||||
SimplifiedAccountingTypeListView.as_view(),
|
||||
name="simple_type_list",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^simple_type/create$",
|
||||
SimplifiedAccountingTypeCreateView.as_view(),
|
||||
name="simple_type_new",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^simple_type/(?P<type_id>[0-9]+)/edit$",
|
||||
SimplifiedAccountingTypeEditView.as_view(),
|
||||
name="simple_type_edit",
|
||||
),
|
||||
# Accounting types
|
||||
re_path(r"^type$", AccountingTypeListView.as_view(), name="type_list"),
|
||||
re_path(r"^type/create$", AccountingTypeCreateView.as_view(), name="type_new"),
|
||||
re_path(
|
||||
url(r"^type$", AccountingTypeListView.as_view(), name="type_list"),
|
||||
url(r"^type/create$", AccountingTypeCreateView.as_view(), name="type_new"),
|
||||
url(
|
||||
r"^type/(?P<type_id>[0-9]+)/edit$",
|
||||
AccountingTypeEditView.as_view(),
|
||||
name="type_edit",
|
||||
),
|
||||
# Bank accounts
|
||||
re_path(r"^$", BankAccountListView.as_view(), name="bank_list"),
|
||||
re_path(r"^bank/create$", BankAccountCreateView.as_view(), name="bank_new"),
|
||||
re_path(
|
||||
url(r"^$", BankAccountListView.as_view(), name="bank_list"),
|
||||
url(r"^bank/create$", BankAccountCreateView.as_view(), name="bank_new"),
|
||||
url(
|
||||
r"^bank/(?P<b_account_id>[0-9]+)$",
|
||||
BankAccountDetailView.as_view(),
|
||||
name="bank_details",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^bank/(?P<b_account_id>[0-9]+)/edit$",
|
||||
BankAccountEditView.as_view(),
|
||||
name="bank_edit",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^bank/(?P<b_account_id>[0-9]+)/delete$",
|
||||
BankAccountDeleteView.as_view(),
|
||||
name="bank_delete",
|
||||
),
|
||||
# Club accounts
|
||||
re_path(r"^club/create$", ClubAccountCreateView.as_view(), name="club_new"),
|
||||
re_path(
|
||||
url(r"^club/create$", ClubAccountCreateView.as_view(), name="club_new"),
|
||||
url(
|
||||
r"^club/(?P<c_account_id>[0-9]+)$",
|
||||
ClubAccountDetailView.as_view(),
|
||||
name="club_details",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^club/(?P<c_account_id>[0-9]+)/edit$",
|
||||
ClubAccountEditView.as_view(),
|
||||
name="club_edit",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^club/(?P<c_account_id>[0-9]+)/delete$",
|
||||
ClubAccountDeleteView.as_view(),
|
||||
name="club_delete",
|
||||
),
|
||||
# Journals
|
||||
re_path(r"^journal/create$", JournalCreateView.as_view(), name="journal_new"),
|
||||
re_path(
|
||||
url(r"^journal/create$", JournalCreateView.as_view(), name="journal_new"),
|
||||
url(
|
||||
r"^journal/(?P<j_id>[0-9]+)$",
|
||||
JournalDetailView.as_view(),
|
||||
name="journal_details",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^journal/(?P<j_id>[0-9]+)/edit$",
|
||||
JournalEditView.as_view(),
|
||||
name="journal_edit",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^journal/(?P<j_id>[0-9]+)/delete$",
|
||||
JournalDeleteView.as_view(),
|
||||
name="journal_delete",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^journal/(?P<j_id>[0-9]+)/statement/nature$",
|
||||
JournalNatureStatementView.as_view(),
|
||||
name="journal_nature_statement",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^journal/(?P<j_id>[0-9]+)/statement/person$",
|
||||
JournalPersonStatementView.as_view(),
|
||||
name="journal_person_statement",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^journal/(?P<j_id>[0-9]+)/statement/accounting$",
|
||||
JournalAccountingStatementView.as_view(),
|
||||
name="journal_accounting_statement",
|
||||
),
|
||||
# Operations
|
||||
re_path(
|
||||
url(
|
||||
r"^operation/create/(?P<j_id>[0-9]+)$",
|
||||
OperationCreateView.as_view(),
|
||||
name="op_new",
|
||||
),
|
||||
re_path(
|
||||
r"^operation/(?P<op_id>[0-9]+)$", OperationEditView.as_view(), name="op_edit"
|
||||
),
|
||||
re_path(
|
||||
url(r"^operation/(?P<op_id>[0-9]+)$", OperationEditView.as_view(), name="op_edit"),
|
||||
url(
|
||||
r"^operation/(?P<op_id>[0-9]+)/pdf$", OperationPDFView.as_view(), name="op_pdf"
|
||||
),
|
||||
# Companies
|
||||
re_path(r"^company/list$", CompanyListView.as_view(), name="co_list"),
|
||||
re_path(r"^company/create$", CompanyCreateView.as_view(), name="co_new"),
|
||||
re_path(r"^company/(?P<co_id>[0-9]+)$", CompanyEditView.as_view(), name="co_edit"),
|
||||
url(r"^company/list$", CompanyListView.as_view(), name="co_list"),
|
||||
url(r"^company/create$", CompanyCreateView.as_view(), name="co_new"),
|
||||
url(r"^company/(?P<co_id>[0-9]+)$", CompanyEditView.as_view(), name="co_edit"),
|
||||
# Labels
|
||||
re_path(r"^label/new$", LabelCreateView.as_view(), name="label_new"),
|
||||
re_path(
|
||||
url(r"^label/new$", LabelCreateView.as_view(), name="label_new"),
|
||||
url(
|
||||
r"^label/(?P<clubaccount_id>[0-9]+)$",
|
||||
LabelListView.as_view(),
|
||||
name="label_list",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^label/(?P<label_id>[0-9]+)/edit$", LabelEditView.as_view(), name="label_edit"
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^label/(?P<label_id>[0-9]+)/delete$",
|
||||
LabelDeleteView.as_view(),
|
||||
name="label_delete",
|
||||
),
|
||||
# User account
|
||||
re_path(r"^refound/account$", RefoundAccountView.as_view(), name="refound_account"),
|
||||
url(r"^refound/account$", RefoundAccountView.as_view(), name="refound_account"),
|
||||
]
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
from django.views.generic import ListView, DetailView
|
||||
from django.views.generic.edit import UpdateView, CreateView, DeleteView, FormView
|
||||
from django.urls import reverse_lazy, reverse
|
||||
from django.core.urlresolvers import reverse_lazy, reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.forms.models import modelform_factory
|
||||
from django.core.exceptions import PermissionDenied, ValidationError
|
||||
|
|
25
api/urls.py
25
api/urls.py
|
@ -22,36 +22,35 @@
|
|||
#
|
||||
#
|
||||
|
||||
from django.urls import re_path, include
|
||||
from django.conf.urls import url, include
|
||||
|
||||
from api.views import *
|
||||
from rest_framework import routers
|
||||
|
||||
# Router config
|
||||
router = routers.DefaultRouter()
|
||||
router.register(r"counter", CounterViewSet, basename="api_counter")
|
||||
router.register(r"user", UserViewSet, basename="api_user")
|
||||
router.register(r"club", ClubViewSet, basename="api_club")
|
||||
router.register(r"group", GroupViewSet, basename="api_group")
|
||||
router.register(r"counter", CounterViewSet, base_name="api_counter")
|
||||
router.register(r"user", UserViewSet, base_name="api_user")
|
||||
router.register(r"club", ClubViewSet, base_name="api_club")
|
||||
router.register(r"group", GroupViewSet, base_name="api_group")
|
||||
|
||||
# Launderette
|
||||
router.register(
|
||||
r"launderette/place", LaunderettePlaceViewSet, basename="api_launderette_place"
|
||||
r"launderette/place", LaunderettePlaceViewSet, base_name="api_launderette_place"
|
||||
)
|
||||
router.register(
|
||||
r"launderette/machine",
|
||||
LaunderetteMachineViewSet,
|
||||
basename="api_launderette_machine",
|
||||
base_name="api_launderette_machine",
|
||||
)
|
||||
router.register(
|
||||
r"launderette/token", LaunderetteTokenViewSet, basename="api_launderette_token"
|
||||
r"launderette/token", LaunderetteTokenViewSet, base_name="api_launderette_token"
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
# API
|
||||
re_path(r"^", include(router.urls)),
|
||||
re_path(r"^login/", include("rest_framework.urls", namespace="rest_framework")),
|
||||
re_path(r"^markdown$", RenderMarkdown, name="api_markdown"),
|
||||
re_path(r"^mailings$", FetchMailingLists, name="mailings_fetch"),
|
||||
re_path(r"^uv$", uv_endpoint, name="uv_endpoint"),
|
||||
url(r"^", include(router.urls)),
|
||||
url(r"^login/", include("rest_framework.urls", namespace="rest_framework")),
|
||||
url(r"^markdown$", RenderMarkdown, name="api_markdown"),
|
||||
url(r"^mailings$", FetchMailingLists, name="mailings_fetch"),
|
||||
]
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
from rest_framework.response import Response
|
||||
from rest_framework import viewsets
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.decorators import detail_route
|
||||
from django.db.models.query import QuerySet
|
||||
|
||||
from core.views import can_view, can_edit
|
||||
|
@ -46,7 +46,7 @@ def check_if(obj, user, test):
|
|||
|
||||
|
||||
class ManageModelMixin:
|
||||
@action(detail=True)
|
||||
@detail_route()
|
||||
def id(self, request, pk=None):
|
||||
"""
|
||||
Get by id (api/v1/router/{pk}/id/)
|
||||
|
@ -77,4 +77,3 @@ from .user import *
|
|||
from .club import *
|
||||
from .group import *
|
||||
from .launderette import *
|
||||
from .uv import *
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
from rest_framework.response import Response
|
||||
from rest_framework.decorators import api_view, renderer_classes
|
||||
from rest_framework.renderers import StaticHTMLRenderer
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from core.templatetags.renderer import markdown
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.decorators import list_route
|
||||
|
||||
from counter.models import Counter
|
||||
|
||||
|
@ -51,7 +51,7 @@ class CounterViewSet(RightModelViewSet):
|
|||
serializer_class = CounterSerializer
|
||||
queryset = Counter.objects.all()
|
||||
|
||||
@action(detail=False)
|
||||
@list_route()
|
||||
def bar(self, request):
|
||||
"""
|
||||
Return all bars (api/v1/counter/bar/)
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.decorators import list_route
|
||||
|
||||
from launderette.models import Launderette, Machine, Token
|
||||
|
||||
|
@ -96,7 +96,7 @@ class LaunderetteTokenViewSet(RightModelViewSet):
|
|||
serializer_class = LaunderetteTokenSerializer
|
||||
queryset = Token.objects.all()
|
||||
|
||||
@action(detail=False)
|
||||
@list_route()
|
||||
def washing(self, request):
|
||||
"""
|
||||
Return all washing tokens (api/v1/launderette/token/washing)
|
||||
|
@ -105,7 +105,7 @@ class LaunderetteTokenViewSet(RightModelViewSet):
|
|||
serializer = self.get_serializer(self.queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False)
|
||||
@list_route()
|
||||
def drying(self, request):
|
||||
"""
|
||||
Return all drying tokens (api/v1/launderette/token/drying)
|
||||
|
@ -114,7 +114,7 @@ class LaunderetteTokenViewSet(RightModelViewSet):
|
|||
serializer = self.get_serializer(self.queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False)
|
||||
@list_route()
|
||||
def avaliable(self, request):
|
||||
"""
|
||||
Return all avaliable tokens (api/v1/launderette/token/avaliable)
|
||||
|
@ -125,7 +125,7 @@ class LaunderetteTokenViewSet(RightModelViewSet):
|
|||
serializer = self.get_serializer(self.queryset, many=True)
|
||||
return Response(serializer.data)
|
||||
|
||||
@action(detail=False)
|
||||
@list_route()
|
||||
def unavaliable(self, request):
|
||||
"""
|
||||
Return all unavaliable tokens (api/v1/launderette/token/unavaliable)
|
||||
|
|
|
@ -26,7 +26,7 @@ import datetime
|
|||
|
||||
from rest_framework import serializers
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.decorators import list_route
|
||||
|
||||
from core.models import User
|
||||
|
||||
|
@ -57,7 +57,7 @@ class UserViewSet(RightModelViewSet):
|
|||
serializer_class = UserSerializer
|
||||
queryset = User.objects.filter(is_active=True)
|
||||
|
||||
@action(detail=False)
|
||||
@list_route()
|
||||
def birthday(self, request):
|
||||
"""
|
||||
Return all users born today (api/v1/user/birstdays)
|
||||
|
|
127
api/views/uv.py
127
api/views/uv.py
|
@ -1,127 +0,0 @@
|
|||
from rest_framework.response import Response
|
||||
from rest_framework.decorators import api_view, renderer_classes
|
||||
from rest_framework.renderers import JSONRenderer
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.conf import settings
|
||||
from rest_framework import serializers
|
||||
import urllib.request
|
||||
import json
|
||||
|
||||
from pedagogy.views import CanCreateUVFunctionMixin
|
||||
|
||||
|
||||
@api_view(["GET"])
|
||||
@renderer_classes((JSONRenderer,))
|
||||
def uv_endpoint(request):
|
||||
if not CanCreateUVFunctionMixin.can_create_uv(request.user):
|
||||
raise PermissionDenied
|
||||
|
||||
params = request.query_params
|
||||
if "year" not in params or "code" not in params:
|
||||
raise serializers.ValidationError("Missing query parameter")
|
||||
|
||||
short_uv, full_uv = find_uv("fr", params["year"], params["code"])
|
||||
if short_uv is None or full_uv is None:
|
||||
return Response(status=204)
|
||||
|
||||
return Response(make_clean_uv(short_uv, full_uv))
|
||||
|
||||
|
||||
def find_uv(lang, year, code):
|
||||
"""
|
||||
Uses the UTBM API to find an UV.
|
||||
short_uv is the UV entry in the UV list. It is returned as it contains
|
||||
information which are not in full_uv.
|
||||
full_uv is the detailed representation of an UV.
|
||||
"""
|
||||
# query the UV list
|
||||
uvs_url = settings.SITH_PEDAGOGY_UTBM_API + "/uvs/{}/{}".format(lang, year)
|
||||
response = urllib.request.urlopen(uvs_url)
|
||||
uvs = json.loads(response.read().decode("utf-8"))
|
||||
|
||||
try:
|
||||
# find the first UV which matches the code
|
||||
short_uv = next(uv for uv in uvs if uv["code"] == code)
|
||||
except StopIteration:
|
||||
return (None, None)
|
||||
|
||||
# get detailed information about the UV
|
||||
uv_url = settings.SITH_PEDAGOGY_UTBM_API + "/uv/{}/{}/{}/{}".format(
|
||||
lang, year, code, short_uv["codeFormation"]
|
||||
)
|
||||
response = urllib.request.urlopen(uv_url)
|
||||
full_uv = json.loads(response.read().decode("utf-8"))
|
||||
|
||||
return (short_uv, full_uv)
|
||||
|
||||
|
||||
def make_clean_uv(short_uv, full_uv):
|
||||
"""
|
||||
Cleans the data up so that it corresponds to our data representation.
|
||||
"""
|
||||
res = {}
|
||||
|
||||
res["credit_type"] = short_uv["codeCategorie"]
|
||||
|
||||
# probably wrong on a few UVs as we pick the first UV we find but
|
||||
# availability depends on the formation
|
||||
semesters = {
|
||||
(True, True): "AUTUMN_AND_SPRING",
|
||||
(True, False): "AUTUMN",
|
||||
(False, True): "SPRING",
|
||||
}
|
||||
res["semester"] = semesters.get(
|
||||
(short_uv["ouvertAutomne"], short_uv["ouvertPrintemps"]), "CLOSED"
|
||||
)
|
||||
|
||||
langs = {"es": "SP", "en": "EN", "de": "DE"}
|
||||
res["language"] = langs.get(full_uv["codeLangue"], "FR")
|
||||
|
||||
if full_uv["departement"] == "Pôle Humanités":
|
||||
res["department"] = "HUMA"
|
||||
else:
|
||||
departments = {
|
||||
"AL": "IMSI",
|
||||
"AE": "EE",
|
||||
"GI": "GI",
|
||||
"GC": "EE",
|
||||
"GM": "MC",
|
||||
"TC": "TC",
|
||||
"GP": "IMSI",
|
||||
"ED": "EDIM",
|
||||
"AI": "GI",
|
||||
"AM": "MC",
|
||||
}
|
||||
res["department"] = departments.get(full_uv["codeFormation"], "NA")
|
||||
|
||||
res["credits"] = full_uv["creditsEcts"]
|
||||
|
||||
activities = ("CM", "TD", "TP", "THE", "TE")
|
||||
for activity in activities:
|
||||
res["hours_{}".format(activity)] = 0
|
||||
for activity in full_uv["activites"]:
|
||||
if activity["code"] in activities:
|
||||
res["hours_{}".format(activity["code"])] += activity["nbh"] // 60
|
||||
|
||||
# wrong if the manager changes depending on the semester
|
||||
semester = full_uv.get("automne", None)
|
||||
if not semester:
|
||||
semester = full_uv.get("printemps", {})
|
||||
res["manager"] = semester.get("responsable", "")
|
||||
|
||||
res["title"] = full_uv["libelle"]
|
||||
|
||||
descriptions = {
|
||||
"objectives": "objectifs",
|
||||
"program": "programme",
|
||||
"skills": "acquisitionCompetences",
|
||||
"key_concepts": "acquisitionNotions",
|
||||
}
|
||||
|
||||
for res_key, full_uv_key in descriptions.items():
|
||||
res[res_key] = full_uv[full_uv_key]
|
||||
# if not found or the API did not return a string
|
||||
if type(res[res_key]) != str:
|
||||
res[res_key] = ""
|
||||
|
||||
return res
|
|
@ -66,7 +66,7 @@ class MailingForm(forms.Form):
|
|||
super(MailingForm, self).__init__(*args, **kwargs)
|
||||
|
||||
self.fields["action"] = forms.TypedChoiceField(
|
||||
choices=(
|
||||
(
|
||||
(self.ACTION_NEW_MAILING, _("New Mailing")),
|
||||
(self.ACTION_NEW_SUBSCRIPTION, _("Subscribe")),
|
||||
(self.ACTION_REMOVE_SUBSCRIPTION, _("Remove")),
|
||||
|
@ -159,13 +159,13 @@ class MailingForm(forms.Form):
|
|||
|
||||
class SellingsFormBase(forms.Form):
|
||||
begin_date = forms.DateTimeField(
|
||||
input_formats=["%Y-%m-%d %H:%M:%S"],
|
||||
["%Y-%m-%d %H:%M:%S"],
|
||||
label=_("Begin date"),
|
||||
required=False,
|
||||
widget=SelectDateTime,
|
||||
)
|
||||
end_date = forms.DateTimeField(
|
||||
input_formats=["%Y-%m-%d %H:%M:%S"],
|
||||
["%Y-%m-%d %H:%M:%S"],
|
||||
label=_("End date"),
|
||||
required=False,
|
||||
widget=SelectDateTime,
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import migrations, models
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -91,10 +90,7 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"club",
|
||||
models.ForeignKey(
|
||||
verbose_name="club",
|
||||
to="club.Club",
|
||||
related_name="members",
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
verbose_name="club", to="club.Club", related_name="members"
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -19,7 +18,6 @@ class Migration(migrations.Migration):
|
|||
model_name="membership",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
verbose_name="user",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
related_name="membership",
|
||||
|
@ -36,7 +34,6 @@ class Migration(migrations.Migration):
|
|||
model_name="club",
|
||||
name="home",
|
||||
field=models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="home_of_club",
|
||||
|
@ -48,21 +45,14 @@ class Migration(migrations.Migration):
|
|||
model_name="club",
|
||||
name="owner_group",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
default=1,
|
||||
to="core.Group",
|
||||
related_name="owned_club",
|
||||
default=1, to="core.Group", related_name="owned_club"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name="club",
|
||||
name="parent",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
null=True,
|
||||
to="club.Club",
|
||||
related_name="children",
|
||||
blank=True,
|
||||
null=True, to="club.Club", related_name="children", blank=True
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -15,7 +14,6 @@ class Migration(migrations.Migration):
|
|||
model_name="membership",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
verbose_name="user",
|
||||
related_name="memberships",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
|
|
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||
from django.conf import settings
|
||||
import re
|
||||
import django.core.validators
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -52,16 +51,12 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"club",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
verbose_name="Club",
|
||||
related_name="mailings",
|
||||
to="club.Club",
|
||||
verbose_name="Club", related_name="mailings", to="club.Club"
|
||||
),
|
||||
),
|
||||
(
|
||||
"moderator",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
null=True,
|
||||
verbose_name="moderator",
|
||||
related_name="moderated_mailings",
|
||||
|
@ -89,7 +84,6 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"mailing",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
verbose_name="Mailing",
|
||||
related_name="subscriptions",
|
||||
to="club.Mailing",
|
||||
|
@ -98,7 +92,6 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
null=True,
|
||||
verbose_name="User",
|
||||
related_name="mailing_subscriptions",
|
||||
|
|
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||
|
||||
from club.models import Club
|
||||
from core.operations import PsqlRunOnly
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
def generate_club_pages(apps, schema_editor):
|
||||
|
@ -32,11 +31,7 @@ class Migration(migrations.Migration):
|
|||
model_name="club",
|
||||
name="page",
|
||||
field=models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="club",
|
||||
blank=True,
|
||||
null=True,
|
||||
to="core.Page",
|
||||
related_name="club", blank=True, null=True, to="core.Page"
|
||||
),
|
||||
),
|
||||
migrations.AddField(
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import migrations, models
|
||||
import club.models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -15,7 +14,6 @@ class Migration(migrations.Migration):
|
|||
model_name="club",
|
||||
name="owner_group",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
default=club.models.Club.get_default_owner_group,
|
||||
related_name="owned_club",
|
||||
to="core.Group",
|
||||
|
|
|
@ -29,7 +29,7 @@ from django.conf import settings
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.core.exceptions import ValidationError, ObjectDoesNotExist
|
||||
from django.db import transaction
|
||||
from django.urls import reverse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils import timezone
|
||||
from django.core.validators import RegexValidator, validate_email
|
||||
from django.utils.functional import cached_property
|
||||
|
@ -46,9 +46,7 @@ class Club(models.Model):
|
|||
|
||||
id = models.AutoField(primary_key=True, db_index=True)
|
||||
name = models.CharField(_("name"), max_length=64)
|
||||
parent = models.ForeignKey(
|
||||
"Club", related_name="children", null=True, blank=True, on_delete=models.CASCADE
|
||||
)
|
||||
parent = models.ForeignKey("Club", related_name="children", null=True, blank=True)
|
||||
unix_name = models.CharField(
|
||||
_("unix name"),
|
||||
max_length=30,
|
||||
|
@ -77,10 +75,7 @@ class Club(models.Model):
|
|||
return settings.SITH_GROUP_ROOT_ID
|
||||
|
||||
owner_group = models.ForeignKey(
|
||||
Group,
|
||||
related_name="owned_club",
|
||||
default=get_default_owner_group,
|
||||
on_delete=models.CASCADE,
|
||||
Group, related_name="owned_club", default=get_default_owner_group
|
||||
)
|
||||
edit_groups = models.ManyToManyField(
|
||||
Group, related_name="editable_club", blank=True
|
||||
|
@ -96,9 +91,7 @@ class Club(models.Model):
|
|||
blank=True,
|
||||
on_delete=models.SET_NULL,
|
||||
)
|
||||
page = models.OneToOneField(
|
||||
Page, related_name="club", blank=True, null=True, on_delete=models.CASCADE
|
||||
)
|
||||
page = models.OneToOneField(Page, related_name="club", blank=True, null=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ["name", "unix_name"]
|
||||
|
@ -190,8 +183,8 @@ class Club(models.Model):
|
|||
name=settings.SITH_MAIN_MEMBERS_GROUP
|
||||
).first()
|
||||
self.make_home()
|
||||
self.home.edit_groups.set([board])
|
||||
self.home.view_groups.set([member, subscribers])
|
||||
self.home.edit_groups = [board]
|
||||
self.home.view_groups = [member, subscribers]
|
||||
self.home.save()
|
||||
self.make_page()
|
||||
|
||||
|
@ -268,15 +261,9 @@ class Membership(models.Model):
|
|||
related_name="memberships",
|
||||
null=False,
|
||||
blank=False,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
club = models.ForeignKey(
|
||||
Club,
|
||||
verbose_name=_("club"),
|
||||
related_name="members",
|
||||
null=False,
|
||||
blank=False,
|
||||
on_delete=models.CASCADE,
|
||||
Club, verbose_name=_("club"), related_name="members", null=False, blank=False
|
||||
)
|
||||
start_date = models.DateField(_("start date"), default=timezone.now)
|
||||
end_date = models.DateField(_("end date"), null=True, blank=True)
|
||||
|
@ -330,12 +317,7 @@ class Mailing(models.Model):
|
|||
"""
|
||||
|
||||
club = models.ForeignKey(
|
||||
Club,
|
||||
verbose_name=_("Club"),
|
||||
related_name="mailings",
|
||||
null=False,
|
||||
blank=False,
|
||||
on_delete=models.CASCADE,
|
||||
Club, verbose_name=_("Club"), related_name="mailings", null=False, blank=False
|
||||
)
|
||||
email = models.CharField(
|
||||
_("Email address"),
|
||||
|
@ -352,11 +334,7 @@ class Mailing(models.Model):
|
|||
)
|
||||
is_moderated = models.BooleanField(_("is moderated"), default=False)
|
||||
moderator = models.ForeignKey(
|
||||
User,
|
||||
related_name="moderated_mailings",
|
||||
verbose_name=_("moderator"),
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
User, related_name="moderated_mailings", verbose_name=_("moderator"), null=True
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
|
@ -431,7 +409,6 @@ class MailingSubscription(models.Model):
|
|||
related_name="subscriptions",
|
||||
null=False,
|
||||
blank=False,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
|
@ -439,7 +416,6 @@ class MailingSubscription(models.Model):
|
|||
related_name="mailing_subscriptions",
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
email = models.EmailField(_("Email address"), blank=False, null=False)
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ from django.conf import settings
|
|||
from django.test import TestCase
|
||||
from django.utils import timezone, html
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.urls import reverse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.management import call_command
|
||||
from django.core.exceptions import ValidationError, NON_FIELD_ERRORS
|
||||
|
||||
|
@ -641,7 +641,7 @@ class MailingFormTest(TestCase):
|
|||
{"action": MailingForm.ACTION_NEW_MAILING, "mailing_email": "mde"},
|
||||
)
|
||||
mde = Mailing.objects.get(email="mde")
|
||||
self.client.post(
|
||||
response = self.client.post(
|
||||
reverse("club:mailing", kwargs={"club_id": self.bdf.id}),
|
||||
{
|
||||
"action": MailingForm.ACTION_NEW_SUBSCRIPTION,
|
||||
|
@ -650,11 +650,6 @@ class MailingFormTest(TestCase):
|
|||
"subscription_mailing": mde.id,
|
||||
},
|
||||
)
|
||||
|
||||
response = self.client.get(
|
||||
reverse("club:mailing", kwargs={"club_id": self.bdf.id})
|
||||
)
|
||||
|
||||
self.assertContains(response, "comunity@git.an")
|
||||
self.assertContains(response, "richard@git.an")
|
||||
self.assertContains(response, "krophil@git.an")
|
||||
|
|
56
club/urls.py
56
club/urls.py
|
@ -23,88 +23,80 @@
|
|||
#
|
||||
#
|
||||
|
||||
from django.urls import re_path
|
||||
from django.conf.urls import url
|
||||
|
||||
from club.views import *
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r"^$", ClubListView.as_view(), name="club_list"),
|
||||
re_path(r"^new$", ClubCreateView.as_view(), name="club_new"),
|
||||
re_path(r"^stats$", ClubStatView.as_view(), name="club_stats"),
|
||||
re_path(r"^(?P<club_id>[0-9]+)/$", ClubView.as_view(), name="club_view"),
|
||||
re_path(
|
||||
url(r"^$", ClubListView.as_view(), name="club_list"),
|
||||
url(r"^new$", ClubCreateView.as_view(), name="club_new"),
|
||||
url(r"^stats$", ClubStatView.as_view(), name="club_stats"),
|
||||
url(r"^(?P<club_id>[0-9]+)/$", ClubView.as_view(), name="club_view"),
|
||||
url(
|
||||
r"^(?P<club_id>[0-9]+)/rev/(?P<rev_id>[0-9]+)/$",
|
||||
ClubRevView.as_view(),
|
||||
name="club_view_rev",
|
||||
),
|
||||
re_path(
|
||||
r"^(?P<club_id>[0-9]+)/hist$", ClubPageHistView.as_view(), name="club_hist"
|
||||
),
|
||||
re_path(r"^(?P<club_id>[0-9]+)/edit$", ClubEditView.as_view(), name="club_edit"),
|
||||
re_path(
|
||||
url(r"^(?P<club_id>[0-9]+)/hist$", ClubPageHistView.as_view(), name="club_hist"),
|
||||
url(r"^(?P<club_id>[0-9]+)/edit$", ClubEditView.as_view(), name="club_edit"),
|
||||
url(
|
||||
r"^(?P<club_id>[0-9]+)/edit/page$",
|
||||
ClubPageEditView.as_view(),
|
||||
name="club_edit_page",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^(?P<club_id>[0-9]+)/members$", ClubMembersView.as_view(), name="club_members"
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^(?P<club_id>[0-9]+)/elderlies$",
|
||||
ClubOldMembersView.as_view(),
|
||||
name="club_old_members",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^(?P<club_id>[0-9]+)/sellings$",
|
||||
ClubSellingView.as_view(),
|
||||
name="club_sellings",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^(?P<club_id>[0-9]+)/sellings/csv$",
|
||||
ClubSellingCSVView.as_view(),
|
||||
name="sellings_csv",
|
||||
),
|
||||
re_path(
|
||||
r"^(?P<club_id>[0-9]+)/prop$", ClubEditPropView.as_view(), name="club_prop"
|
||||
),
|
||||
re_path(r"^(?P<club_id>[0-9]+)/tools$", ClubToolsView.as_view(), name="tools"),
|
||||
re_path(
|
||||
r"^(?P<club_id>[0-9]+)/mailing$", ClubMailingView.as_view(), name="mailing"
|
||||
),
|
||||
re_path(
|
||||
url(r"^(?P<club_id>[0-9]+)/prop$", ClubEditPropView.as_view(), name="club_prop"),
|
||||
url(r"^(?P<club_id>[0-9]+)/tools$", ClubToolsView.as_view(), name="tools"),
|
||||
url(r"^(?P<club_id>[0-9]+)/mailing$", ClubMailingView.as_view(), name="mailing"),
|
||||
url(
|
||||
r"^(?P<mailing_id>[0-9]+)/mailing/generate$",
|
||||
MailingAutoGenerationView.as_view(),
|
||||
name="mailing_generate",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^(?P<mailing_id>[0-9]+)/mailing/delete$",
|
||||
MailingDeleteView.as_view(),
|
||||
name="mailing_delete",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^(?P<mailing_subscription_id>[0-9]+)/mailing/delete/subscription$",
|
||||
MailingSubscriptionDeleteView.as_view(),
|
||||
name="mailing_subscription_delete",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^membership/(?P<membership_id>[0-9]+)/set_old$",
|
||||
MembershipSetOldView.as_view(),
|
||||
name="membership_set_old",
|
||||
),
|
||||
re_path(
|
||||
r"^(?P<club_id>[0-9]+)/poster$", PosterListView.as_view(), name="poster_list"
|
||||
),
|
||||
re_path(
|
||||
url(r"^(?P<club_id>[0-9]+)/poster$", PosterListView.as_view(), name="poster_list"),
|
||||
url(
|
||||
r"^(?P<club_id>[0-9]+)/poster/create$",
|
||||
PosterCreateView.as_view(),
|
||||
name="poster_create",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^(?P<club_id>[0-9]+)/poster/(?P<poster_id>[0-9]+)/edit$",
|
||||
PosterEditView.as_view(),
|
||||
name="poster_edit",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^(?P<club_id>[0-9]+)/poster/(?P<poster_id>[0-9]+)/delete$",
|
||||
PosterDeleteView.as_view(),
|
||||
name="poster_delete",
|
||||
|
|
|
@ -31,7 +31,7 @@ from django.views.generic.edit import DeleteView
|
|||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.views.generic.edit import UpdateView, CreateView
|
||||
from django.http import HttpResponseRedirect, HttpResponse, Http404
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import ugettext as _t
|
||||
|
@ -451,7 +451,7 @@ class ClubEditPropView(ClubTabsMixin, CanEditPropMixin, UpdateView):
|
|||
current_tab = "props"
|
||||
|
||||
|
||||
class ClubCreateView(CanCreateMixin, CreateView):
|
||||
class ClubCreateView(CanEditPropMixin, CreateView):
|
||||
"""
|
||||
Create a club (for the Sith admin)
|
||||
"""
|
||||
|
@ -574,8 +574,7 @@ class ClubMailingView(ClubTabsMixin, CanEditMixin, DetailFormView):
|
|||
except ValidationError as validation_error:
|
||||
return validation_error
|
||||
|
||||
sub.save()
|
||||
users_to_save.append(sub)
|
||||
users_to_save.append(sub.save())
|
||||
|
||||
if cleaned_data["subscription_email"]:
|
||||
sub = MailingSubscription(
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -51,7 +50,6 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"author",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="owned_news",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="author",
|
||||
|
@ -60,16 +58,12 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"club",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="news",
|
||||
to="club.Club",
|
||||
verbose_name="club",
|
||||
related_name="news", to="club.Club", verbose_name="club"
|
||||
),
|
||||
),
|
||||
(
|
||||
"moderator",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="moderated_news",
|
||||
null=True,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
|
@ -105,10 +99,7 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"news",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="dates",
|
||||
to="com.News",
|
||||
verbose_name="news_date",
|
||||
related_name="dates", to="com.News", verbose_name="news_date"
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -57,7 +56,6 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"author",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
verbose_name="author",
|
||||
related_name="owned_weekmail_articles",
|
||||
|
@ -66,7 +64,6 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"club",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="club.Club",
|
||||
verbose_name="club",
|
||||
related_name="weekmail_articles",
|
||||
|
@ -75,7 +72,6 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"weekmail",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="com.Weekmail",
|
||||
verbose_name="weekmail",
|
||||
related_name="articles",
|
||||
|
|
|
@ -4,7 +4,6 @@ from __future__ import unicode_literals
|
|||
from django.db import migrations, models
|
||||
import django.utils.timezone
|
||||
from django.conf import settings
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -49,16 +48,12 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"club",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
verbose_name="club",
|
||||
related_name="posters",
|
||||
to="club.Club",
|
||||
verbose_name="club", related_name="posters", to="club.Club"
|
||||
),
|
||||
),
|
||||
(
|
||||
"moderator",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
verbose_name="moderator",
|
||||
blank=True,
|
||||
null=True,
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.23 on 2019-08-18 17:00
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("com", "0005_auto_20180318_2227")]
|
||||
|
||||
operations = [migrations.RemoveField(model_name="sith", name="index_page")]
|
|
@ -28,7 +28,7 @@ from django.db import models, transaction
|
|||
from django.db.models import Q
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from django.urls import reverse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
from django.contrib.staticfiles.templatetags.staticfiles import static
|
||||
from django.core.mail import EmailMultiAlternatives
|
||||
|
@ -45,6 +45,7 @@ class Sith(models.Model):
|
|||
|
||||
alert_msg = models.TextField(_("alert message"), default="", blank=True)
|
||||
info_msg = models.TextField(_("info message"), default="", blank=True)
|
||||
index_page = models.TextField(_("index page"), default="", blank=True)
|
||||
weekmail_destinations = models.TextField(_("weekmail destinations"), default="")
|
||||
|
||||
def is_owned_by(self, user):
|
||||
|
@ -71,22 +72,13 @@ class News(models.Model):
|
|||
type = models.CharField(
|
||||
_("type"), max_length=16, choices=NEWS_TYPES, default="EVENT"
|
||||
)
|
||||
club = models.ForeignKey(
|
||||
Club, related_name="news", verbose_name=_("club"), on_delete=models.CASCADE
|
||||
)
|
||||
club = models.ForeignKey(Club, related_name="news", verbose_name=_("club"))
|
||||
author = models.ForeignKey(
|
||||
User,
|
||||
related_name="owned_news",
|
||||
verbose_name=_("author"),
|
||||
on_delete=models.CASCADE,
|
||||
User, related_name="owned_news", verbose_name=_("author")
|
||||
)
|
||||
is_moderated = models.BooleanField(_("is moderated"), default=False)
|
||||
moderator = models.ForeignKey(
|
||||
User,
|
||||
related_name="moderated_news",
|
||||
verbose_name=_("moderator"),
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
User, related_name="moderated_news", verbose_name=_("moderator"), null=True
|
||||
)
|
||||
|
||||
def is_owned_by(self, user):
|
||||
|
@ -147,12 +139,7 @@ class NewsDate(models.Model):
|
|||
we don't have to make copies
|
||||
"""
|
||||
|
||||
news = models.ForeignKey(
|
||||
News,
|
||||
related_name="dates",
|
||||
verbose_name=_("news_date"),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
news = models.ForeignKey(News, related_name="dates", verbose_name=_("news_date"))
|
||||
start_date = models.DateTimeField(_("start_date"), null=True, blank=True)
|
||||
end_date = models.DateTimeField(_("end_date"), null=True, blank=True)
|
||||
|
||||
|
@ -163,13 +150,6 @@ class NewsDate(models.Model):
|
|||
class Weekmail(models.Model):
|
||||
"""
|
||||
The weekmail class
|
||||
|
||||
:ivar title: Title of the weekmail
|
||||
:ivar intro: Introduction of the weekmail
|
||||
:ivar joke: Joke of the week
|
||||
:ivar protip: Tip of the week
|
||||
:ivar conclusion: Conclusion of the weekmail
|
||||
:ivar sent: Track if the weekmail has been sent
|
||||
"""
|
||||
|
||||
title = models.CharField(_("title"), max_length=64, blank=True)
|
||||
|
@ -183,10 +163,6 @@ class Weekmail(models.Model):
|
|||
ordering = ["-id"]
|
||||
|
||||
def send(self):
|
||||
"""
|
||||
Send the weekmail to all users with the receive weekmail option opt-in.
|
||||
Also send the weekmail to the mailing list in settings.SITH_COM_EMAIL.
|
||||
"""
|
||||
dest = [
|
||||
i[0]
|
||||
for i in Preferences.objects.filter(receive_weekmail=True).values_list(
|
||||
|
@ -208,32 +184,20 @@ class Weekmail(models.Model):
|
|||
Weekmail().save()
|
||||
|
||||
def render_text(self):
|
||||
"""
|
||||
Renders a pure text version of the mail for readers without HTML support.
|
||||
"""
|
||||
return render(
|
||||
None, "com/weekmail_renderer_text.jinja", context={"weekmail": self}
|
||||
).content.decode("utf-8")
|
||||
|
||||
def render_html(self):
|
||||
"""
|
||||
Renders an HTML version of the mail with images and fancy CSS.
|
||||
"""
|
||||
return render(
|
||||
None, "com/weekmail_renderer_html.jinja", context={"weekmail": self}
|
||||
).content.decode("utf-8")
|
||||
|
||||
def get_banner(self):
|
||||
"""
|
||||
Return an absolute link to the banner.
|
||||
"""
|
||||
return "http://" + settings.SITH_URL + static("com/img/weekmail_bannerA19.jpg")
|
||||
return "http://" + settings.SITH_URL + static("com/img/weekmail_bannerA18.jpg")
|
||||
|
||||
def get_footer(self):
|
||||
"""
|
||||
Return an absolute link to the footer.
|
||||
"""
|
||||
return "http://" + settings.SITH_URL + static("com/img/weekmail_footerA19.jpg")
|
||||
return "http://" + settings.SITH_URL + static("com/img/weekmail_footerA18.jpg")
|
||||
|
||||
def __str__(self):
|
||||
return "Weekmail %s (sent: %s) - %s" % (self.id, self.sent, self.title)
|
||||
|
@ -244,25 +208,15 @@ class Weekmail(models.Model):
|
|||
|
||||
class WeekmailArticle(models.Model):
|
||||
weekmail = models.ForeignKey(
|
||||
Weekmail,
|
||||
related_name="articles",
|
||||
verbose_name=_("weekmail"),
|
||||
null=True,
|
||||
on_delete=models.CASCADE,
|
||||
Weekmail, related_name="articles", verbose_name=_("weekmail"), null=True
|
||||
)
|
||||
title = models.CharField(_("title"), max_length=64)
|
||||
content = models.TextField(_("content"))
|
||||
author = models.ForeignKey(
|
||||
User,
|
||||
related_name="owned_weekmail_articles",
|
||||
verbose_name=_("author"),
|
||||
on_delete=models.CASCADE,
|
||||
User, related_name="owned_weekmail_articles", verbose_name=_("author")
|
||||
)
|
||||
club = models.ForeignKey(
|
||||
Club,
|
||||
related_name="weekmail_articles",
|
||||
verbose_name=_("club"),
|
||||
on_delete=models.CASCADE,
|
||||
Club, related_name="weekmail_articles", verbose_name=_("club")
|
||||
)
|
||||
rank = models.IntegerField(_("rank"), default=-1)
|
||||
|
||||
|
@ -295,11 +249,7 @@ class Poster(models.Model):
|
|||
)
|
||||
file = models.ImageField(_("file"), null=False, upload_to="com/posters")
|
||||
club = models.ForeignKey(
|
||||
Club,
|
||||
related_name="posters",
|
||||
verbose_name=_("club"),
|
||||
null=False,
|
||||
on_delete=models.CASCADE,
|
||||
Club, related_name="posters", verbose_name=_("club"), null=False
|
||||
)
|
||||
screens = models.ManyToManyField(Screen, related_name="posters")
|
||||
date_begin = models.DateTimeField(blank=False, null=False, default=timezone.now)
|
||||
|
@ -314,7 +264,6 @@ class Poster(models.Model):
|
|||
verbose_name=_("moderator"),
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
|
|
|
@ -40,29 +40,22 @@
|
|||
|
||||
<div id="birthdays">
|
||||
<div id="birthdays_title">{% trans %}Birthdays{% endtrans %}</div>
|
||||
<div id="birthdays_content">
|
||||
{% if user.is_subscribed %}
|
||||
{# Cache request for 1 hour #}
|
||||
{% cache 3600 birthdays %}
|
||||
<ul class="birthdays_year">
|
||||
{% for d in birthdays.dates('date_of_birth', 'year', 'DESC') %}
|
||||
<li>
|
||||
{% trans age=timezone.now().year - d.year %}{{ age }} year old{% endtrans %}
|
||||
<ul>
|
||||
{% for u in birthdays.filter(date_of_birth__year=d.year) %}
|
||||
<li><a href="{{ u.get_absolute_url() }}">{{ u.get_short_name() }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</li>
|
||||
{% endfor %}
|
||||
<div id="birthdays_content">
|
||||
<ul class="birthdays_year">
|
||||
{% for d in birthdays.dates('date_of_birth', 'year', 'DESC') %}
|
||||
<li>
|
||||
{% trans age=timezone.now().year - d.year %}{{ age }} year old{% endtrans %}
|
||||
<ul>
|
||||
{% for u in birthdays.filter(date_of_birth__year=d.year) %}
|
||||
<li><a href="{{ u.get_absolute_url() }}">{{ u.get_short_name() }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endcache %}
|
||||
{% else %}
|
||||
<p>{% trans %}You need an up to date subscription to access this content{% endtrans %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div id="left_column" class="news_column">
|
||||
|
|
49
com/tests.py
49
com/tests.py
|
@ -24,37 +24,12 @@
|
|||
|
||||
from django.test import TestCase
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.management import call_command
|
||||
from django.utils import html
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
from core.models import User, RealGroup
|
||||
|
||||
|
||||
class ComAlertTest(TestCase):
|
||||
def setUp(self):
|
||||
call_command("populate")
|
||||
|
||||
def test_page_is_working(self):
|
||||
self.client.login(username="comunity", password="plop")
|
||||
response = self.client.get(reverse("com:alert_edit"))
|
||||
self.assertNotEquals(response.status_code, 500)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
|
||||
class ComInfoTest(TestCase):
|
||||
def setUp(self):
|
||||
call_command("populate")
|
||||
|
||||
def test_page_is_working(self):
|
||||
self.client.login(username="comunity", password="plop")
|
||||
response = self.client.get(reverse("com:info_edit"))
|
||||
self.assertNotEquals(response.status_code, 500)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
|
||||
class ComTest(TestCase):
|
||||
def setUp(self):
|
||||
call_command("populate")
|
||||
|
@ -62,7 +37,7 @@ class ComTest(TestCase):
|
|||
self.com_group = RealGroup.objects.filter(
|
||||
id=settings.SITH_GROUP_COM_ADMIN_ID
|
||||
).first()
|
||||
self.skia.groups.set([self.com_group])
|
||||
self.skia.groups = [self.com_group]
|
||||
self.skia.save()
|
||||
self.client.login(username=self.skia.username, password="plop")
|
||||
|
||||
|
@ -99,23 +74,3 @@ class ComTest(TestCase):
|
|||
"""<div id="info_box">\\n <div class="markdown"><h3>INFO: <strong>Caaaataaaapuuuulte!!!!</strong></h3>"""
|
||||
in str(r.content)
|
||||
)
|
||||
|
||||
def test_birthday_non_subscribed_user(self):
|
||||
self.client.login(username="guy", password="plop")
|
||||
response = self.client.get(reverse("core:index"))
|
||||
self.assertContains(
|
||||
response,
|
||||
text=html.escape(
|
||||
_("You need an up to date subscription to access this content")
|
||||
),
|
||||
)
|
||||
|
||||
def test_birthday_subscibed_user(self):
|
||||
response = self.client.get(reverse("core:index"))
|
||||
|
||||
self.assertNotContains(
|
||||
response,
|
||||
text=html.escape(
|
||||
_("You need an up to date subscription to access this content")
|
||||
),
|
||||
)
|
||||
|
|
67
com/urls.py
67
com/urls.py
|
@ -22,103 +22,98 @@
|
|||
#
|
||||
#
|
||||
|
||||
from django.urls import re_path
|
||||
from django.conf.urls import url
|
||||
|
||||
from com.views import *
|
||||
from club.views import MailingDeleteView
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r"^sith/edit/alert$", AlertMsgEditView.as_view(), name="alert_edit"),
|
||||
re_path(r"^sith/edit/info$", InfoMsgEditView.as_view(), name="info_edit"),
|
||||
re_path(
|
||||
url(r"^sith/edit/alert$", AlertMsgEditView.as_view(), name="alert_edit"),
|
||||
url(r"^sith/edit/info$", InfoMsgEditView.as_view(), name="info_edit"),
|
||||
url(r"^sith/edit/index$", IndexEditView.as_view(), name="index_edit"),
|
||||
url(
|
||||
r"^sith/edit/weekmail_destinations$",
|
||||
WeekmailDestinationEditView.as_view(),
|
||||
name="weekmail_destinations",
|
||||
),
|
||||
re_path(r"^weekmail$", WeekmailEditView.as_view(), name="weekmail"),
|
||||
re_path(
|
||||
r"^weekmail/preview$", WeekmailPreviewView.as_view(), name="weekmail_preview"
|
||||
),
|
||||
re_path(
|
||||
url(r"^weekmail$", WeekmailEditView.as_view(), name="weekmail"),
|
||||
url(r"^weekmail/preview$", WeekmailPreviewView.as_view(), name="weekmail_preview"),
|
||||
url(
|
||||
r"^weekmail/new_article$",
|
||||
WeekmailArticleCreateView.as_view(),
|
||||
name="weekmail_article",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^weekmail/article/(?P<article_id>[0-9]+)/delete$",
|
||||
WeekmailArticleDeleteView.as_view(),
|
||||
name="weekmail_article_delete",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^weekmail/article/(?P<article_id>[0-9]+)/edit$",
|
||||
WeekmailArticleEditView.as_view(),
|
||||
name="weekmail_article_edit",
|
||||
),
|
||||
re_path(r"^news$", NewsListView.as_view(), name="news_list"),
|
||||
re_path(r"^news/admin$", NewsAdminListView.as_view(), name="news_admin_list"),
|
||||
re_path(r"^news/create$", NewsCreateView.as_view(), name="news_new"),
|
||||
re_path(
|
||||
url(r"^news$", NewsListView.as_view(), name="news_list"),
|
||||
url(r"^news/admin$", NewsAdminListView.as_view(), name="news_admin_list"),
|
||||
url(r"^news/create$", NewsCreateView.as_view(), name="news_new"),
|
||||
url(
|
||||
r"^news/(?P<news_id>[0-9]+)/delete$",
|
||||
NewsDeleteView.as_view(),
|
||||
name="news_delete",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^news/(?P<news_id>[0-9]+)/moderate$",
|
||||
NewsModerateView.as_view(),
|
||||
name="news_moderate",
|
||||
),
|
||||
re_path(
|
||||
r"^news/(?P<news_id>[0-9]+)/edit$", NewsEditView.as_view(), name="news_edit"
|
||||
),
|
||||
re_path(
|
||||
r"^news/(?P<news_id>[0-9]+)$", NewsDetailView.as_view(), name="news_detail"
|
||||
),
|
||||
re_path(r"^mailings$", MailingListAdminView.as_view(), name="mailing_admin"),
|
||||
re_path(
|
||||
url(r"^news/(?P<news_id>[0-9]+)/edit$", NewsEditView.as_view(), name="news_edit"),
|
||||
url(r"^news/(?P<news_id>[0-9]+)$", NewsDetailView.as_view(), name="news_detail"),
|
||||
url(r"^mailings$", MailingListAdminView.as_view(), name="mailing_admin"),
|
||||
url(
|
||||
r"^mailings/(?P<mailing_id>[0-9]+)/moderate$",
|
||||
MailingModerateView.as_view(),
|
||||
name="mailing_moderate",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^mailings/(?P<mailing_id>[0-9]+)/delete$",
|
||||
MailingDeleteView.as_view(redirect_page="com:mailing_admin"),
|
||||
name="mailing_delete",
|
||||
),
|
||||
re_path(r"^poster$", PosterListView.as_view(), name="poster_list"),
|
||||
re_path(r"^poster/create$", PosterCreateView.as_view(), name="poster_create"),
|
||||
re_path(
|
||||
url(r"^poster$", PosterListView.as_view(), name="poster_list"),
|
||||
url(r"^poster/create$", PosterCreateView.as_view(), name="poster_create"),
|
||||
url(
|
||||
r"^poster/(?P<poster_id>[0-9]+)/edit$",
|
||||
PosterEditView.as_view(),
|
||||
name="poster_edit",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^poster/(?P<poster_id>[0-9]+)/delete$",
|
||||
PosterDeleteView.as_view(),
|
||||
name="poster_delete",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^poster/moderate$",
|
||||
PosterModerateListView.as_view(),
|
||||
name="poster_moderate_list",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^poster/(?P<object_id>[0-9]+)/moderate$",
|
||||
PosterModerateView.as_view(),
|
||||
name="poster_moderate",
|
||||
),
|
||||
re_path(r"^screen$", ScreenListView.as_view(), name="screen_list"),
|
||||
re_path(r"^screen/create$", ScreenCreateView.as_view(), name="screen_create"),
|
||||
re_path(
|
||||
url(r"^screen$", ScreenListView.as_view(), name="screen_list"),
|
||||
url(r"^screen/create$", ScreenCreateView.as_view(), name="screen_create"),
|
||||
url(
|
||||
r"^screen/(?P<screen_id>[0-9]+)/slideshow$",
|
||||
ScreenSlideshowView.as_view(),
|
||||
name="screen_slideshow",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^screen/(?P<screen_id>[0-9]+)/edit$",
|
||||
ScreenEditView.as_view(),
|
||||
name="screen_edit",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^screen/(?P<screen_id>[0-9]+)/delete$",
|
||||
ScreenDeleteView.as_view(),
|
||||
name="screen_delete",
|
||||
|
|
34
com/views.py
34
com/views.py
|
@ -29,7 +29,7 @@ from django.views.generic import ListView, DetailView, View
|
|||
from django.views.generic.edit import UpdateView, CreateView, DeleteView
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.urls import reverse, reverse_lazy
|
||||
from django.core.urlresolvers import reverse, reverse_lazy
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
|
@ -74,14 +74,14 @@ class PosterForm(forms.ModelForm):
|
|||
widgets = {"screens": forms.CheckboxSelectMultiple}
|
||||
|
||||
date_begin = forms.DateTimeField(
|
||||
input_formats=["%Y-%m-%d %H:%M:%S"],
|
||||
["%Y-%m-%d %H:%M:%S"],
|
||||
label=_("Start date"),
|
||||
widget=SelectDateTime,
|
||||
required=True,
|
||||
initial=timezone.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
)
|
||||
date_end = forms.DateTimeField(
|
||||
input_formats=["%Y-%m-%d %H:%M:%S"],
|
||||
["%Y-%m-%d %H:%M:%S"],
|
||||
label=_("End date"),
|
||||
widget=SelectDateTime,
|
||||
required=False,
|
||||
|
@ -114,6 +114,9 @@ class ComTabsMixin(TabedViewMixin):
|
|||
"name": _("Weekmail destinations"),
|
||||
}
|
||||
)
|
||||
tab_list.append(
|
||||
{"url": reverse("com:index_edit"), "slug": "index", "name": _("Index page")}
|
||||
)
|
||||
tab_list.append(
|
||||
{"url": reverse("com:info_edit"), "slug": "info", "name": _("Info message")}
|
||||
)
|
||||
|
@ -179,6 +182,14 @@ class InfoMsgEditView(ComEditView):
|
|||
success_url = reverse_lazy("com:info_edit")
|
||||
|
||||
|
||||
class IndexEditView(ComEditView):
|
||||
form_class = modelform_factory(
|
||||
Sith, fields=["index_page"], widgets={"index_page": MarkdownInput}
|
||||
)
|
||||
current_tab = "index"
|
||||
success_url = reverse_lazy("com:index_edit")
|
||||
|
||||
|
||||
class WeekmailDestinationEditView(ComEditView):
|
||||
fields = ["weekmail_destinations"]
|
||||
current_tab = "weekmail_destinations"
|
||||
|
@ -200,22 +211,19 @@ class NewsForm(forms.ModelForm):
|
|||
}
|
||||
|
||||
start_date = forms.DateTimeField(
|
||||
input_formats=["%Y-%m-%d %H:%M:%S"],
|
||||
["%Y-%m-%d %H:%M:%S"],
|
||||
label=_("Start date"),
|
||||
widget=SelectDateTime,
|
||||
required=False,
|
||||
)
|
||||
end_date = forms.DateTimeField(
|
||||
input_formats=["%Y-%m-%d %H:%M:%S"],
|
||||
["%Y-%m-%d %H:%M:%S"],
|
||||
label=_("End date"),
|
||||
widget=SelectDateTime,
|
||||
required=False,
|
||||
)
|
||||
until = forms.DateTimeField(
|
||||
input_formats=["%Y-%m-%d %H:%M:%S"],
|
||||
label=_("Until"),
|
||||
widget=SelectDateTime,
|
||||
required=False,
|
||||
["%Y-%m-%d %H:%M:%S"], label=_("Until"), widget=SelectDateTime, required=False
|
||||
)
|
||||
automoderation = forms.BooleanField(label=_("Automoderation"), required=False)
|
||||
|
||||
|
@ -230,11 +238,7 @@ class NewsForm(forms.ModelForm):
|
|||
self.add_error(
|
||||
"end_date", ValidationError(_("This field is required."))
|
||||
)
|
||||
if (
|
||||
not self.has_error("start_date")
|
||||
and not self.has_error("end_date")
|
||||
and self.cleaned_data["start_date"] > self.cleaned_data["end_date"]
|
||||
):
|
||||
if self.cleaned_data["start_date"] > self.cleaned_data["end_date"]:
|
||||
self.add_error(
|
||||
"end_date",
|
||||
ValidationError(
|
||||
|
@ -746,7 +750,7 @@ class PosterEditBaseView(UpdateView):
|
|||
|
||||
def get_context_data(self, **kwargs):
|
||||
kwargs = super(PosterEditBaseView, self).get_context_data(**kwargs)
|
||||
if hasattr(self, "club"):
|
||||
if not self.request.user.is_com_admin:
|
||||
kwargs["club"] = self.club
|
||||
return kwargs
|
||||
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding:utf-8 -*
|
||||
#
|
||||
# Copyright 2019
|
||||
# - Sli <antoine@bartuccio.fr>
|
||||
#
|
||||
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
|
||||
# http://ae.utbm.fr.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License a 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 General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
|
||||
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
import os
|
||||
from django.core.management.commands import compilemessages
|
||||
|
||||
|
||||
class Command(compilemessages.Command):
|
||||
"""
|
||||
Wrap call to compilemessages to avoid building whole env
|
||||
"""
|
||||
|
||||
help = """
|
||||
The usage is the same as the real compilemessages
|
||||
but it goes into the sith dir first
|
||||
"""
|
||||
|
||||
def handle(self, *args, **options):
|
||||
|
||||
os.chdir("sith")
|
||||
super(Command, self).handle(*args, **options)
|
|
@ -1,71 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
# -*- coding:utf-8 -*
|
||||
#
|
||||
# Copyright 2019
|
||||
# - Sli <antoine@bartuccio.fr>
|
||||
#
|
||||
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
|
||||
# http://ae.utbm.fr.
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify it under
|
||||
# the terms of the GNU General Public License a 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 General Public License for more
|
||||
# details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License along with
|
||||
# this program; if not, write to the Free Sofware Foundation, Inc., 59 Temple
|
||||
# Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
#
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
from http.server import test, CGIHTTPRequestHandler
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
|
||||
# TODO Django 2.2 : implement autoreload following
|
||||
# https://stackoverflow.com/questions/42907285/django-autoreload-add-watched-file
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
help = "Generate Sphinx documentation and launch basic server"
|
||||
|
||||
default_addr = "127.0.0.1"
|
||||
default_port = "8080"
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"addrport", nargs="?", help="Optional port number, or ipaddr:port"
|
||||
)
|
||||
|
||||
def handle(self, *args, **kwargs):
|
||||
os.chdir("doc")
|
||||
err = os.system("make html")
|
||||
|
||||
if err != 0:
|
||||
self.stdout.write("A build error occured, exiting")
|
||||
sys.exit(err)
|
||||
|
||||
os.chdir("_build/html")
|
||||
addr = self.default_addr
|
||||
port = self.default_port
|
||||
if kwargs["addrport"]:
|
||||
addrport = kwargs["addrport"].split(":")
|
||||
|
||||
addr = addrport[0]
|
||||
|
||||
if len(addrport) > 1:
|
||||
port = addrport[1]
|
||||
|
||||
if not port.isnumeric():
|
||||
self.stdout.write("%s is not a valid port" % (port,))
|
||||
sys.exit(0)
|
||||
|
||||
test(HandlerClass=CGIHTTPRequestHandler, port=int(port), bind=addr)
|
|
@ -52,7 +52,6 @@ from counter.models import Customer, ProductType, Product, Counter, Selling, Stu
|
|||
from com.models import Sith, Weekmail, News, NewsDate
|
||||
from election.models import Election, Role, Candidature, ElectionList
|
||||
from forum.models import Forum, ForumTopic
|
||||
from pedagogy.models import UV
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
@ -85,7 +84,6 @@ class Command(BaseCommand):
|
|||
Group(name="Banned to subscribe").save()
|
||||
Group(name="SAS admin").save()
|
||||
Group(name="Forum admin").save()
|
||||
Group(name="Pedagogy admin").save()
|
||||
self.reset_index("core", "auth")
|
||||
root = User(
|
||||
id=0,
|
||||
|
@ -142,18 +140,18 @@ class Command(BaseCommand):
|
|||
g.save()
|
||||
c = Counter(id=b[0], name=b[1], club=bar_club, type="BAR")
|
||||
c.save()
|
||||
g.editable_counters.add(c)
|
||||
g.save()
|
||||
c.edit_groups = [g]
|
||||
c.save()
|
||||
self.reset_index("counter")
|
||||
Counter(name="Eboutic", club=main_club, type="EBOUTIC").save()
|
||||
Counter(name="AE", club=main_club, type="OFFICE").save()
|
||||
|
||||
home_root.view_groups.set(
|
||||
[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()]
|
||||
)
|
||||
club_root.view_groups.set(
|
||||
[Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()]
|
||||
)
|
||||
home_root.view_groups = [
|
||||
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()
|
||||
]
|
||||
club_root.view_groups = [
|
||||
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first()
|
||||
]
|
||||
home_root.save()
|
||||
club_root.save()
|
||||
|
||||
|
@ -163,7 +161,7 @@ class Command(BaseCommand):
|
|||
p = Page(name="Index")
|
||||
p.set_lock(root)
|
||||
p.save()
|
||||
p.view_groups.set([settings.SITH_GROUP_PUBLIC_ID])
|
||||
p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
|
||||
p.set_lock(root)
|
||||
p.save()
|
||||
PageRev(
|
||||
|
@ -178,7 +176,7 @@ Welcome to the wiki page!
|
|||
p = Page(name="services")
|
||||
p.set_lock(root)
|
||||
p.save()
|
||||
p.view_groups.set([settings.SITH_GROUP_PUBLIC_ID])
|
||||
p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
|
||||
p.set_lock(root)
|
||||
PageRev(
|
||||
page=p,
|
||||
|
@ -297,13 +295,9 @@ Welcome to the wiki page!
|
|||
counter.view_groups = [
|
||||
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
|
||||
]
|
||||
counter.groups.set(
|
||||
[
|
||||
Group.objects.filter(id=settings.SITH_GROUP_COUNTER_ADMIN_ID)
|
||||
.first()
|
||||
.id
|
||||
]
|
||||
)
|
||||
counter.groups = [
|
||||
Group.objects.filter(id=settings.SITH_GROUP_COUNTER_ADMIN_ID).first().id
|
||||
]
|
||||
counter.save()
|
||||
# Adding user Comptable
|
||||
comptable = User(
|
||||
|
@ -320,13 +314,11 @@ Welcome to the wiki page!
|
|||
comptable.view_groups = [
|
||||
Group.objects.filter(name=settings.SITH_MAIN_MEMBERS_GROUP).first().id
|
||||
]
|
||||
comptable.groups.set(
|
||||
[
|
||||
Group.objects.filter(id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||
.first()
|
||||
.id
|
||||
]
|
||||
)
|
||||
comptable.groups = [
|
||||
Group.objects.filter(id=settings.SITH_GROUP_ACCOUNTING_ADMIN_ID)
|
||||
.first()
|
||||
.id
|
||||
]
|
||||
comptable.save()
|
||||
# Adding user Guy
|
||||
u = User(
|
||||
|
@ -365,11 +357,11 @@ Welcome to the wiki page!
|
|||
PageRev(
|
||||
page=p, title="Aide sur la syntaxe", author=skia, content=rm.read()
|
||||
).save()
|
||||
p.view_groups.set([settings.SITH_GROUP_PUBLIC_ID])
|
||||
p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
|
||||
p.save(force_lock=True)
|
||||
p = Page(name="Services")
|
||||
p.save(force_lock=True)
|
||||
p.view_groups.set([settings.SITH_GROUP_PUBLIC_ID])
|
||||
p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
|
||||
p.save(force_lock=True)
|
||||
PageRev(
|
||||
page=p,
|
||||
|
@ -383,6 +375,13 @@ Welcome to the wiki page!
|
|||
|
||||
""",
|
||||
).save()
|
||||
# Adding README
|
||||
p = Page(name="README")
|
||||
p.save(force_lock=True)
|
||||
p.view_groups = [settings.SITH_GROUP_PUBLIC_ID]
|
||||
p.save(force_lock=True)
|
||||
with open(os.path.join(root_path) + "/README.md", "r") as rm:
|
||||
PageRev(page=p, title="README", author=skia, content=rm.read()).save()
|
||||
|
||||
# Subscription
|
||||
default_subscription = "un-semestre"
|
||||
|
@ -848,9 +847,9 @@ Welcome to the wiki page!
|
|||
)
|
||||
comunity.set_password("plop")
|
||||
comunity.save()
|
||||
comunity.groups.set(
|
||||
[Group.objects.filter(name="Communication admin").first().id]
|
||||
)
|
||||
comunity.groups = [
|
||||
Group.objects.filter(name="Communication admin").first().id
|
||||
]
|
||||
comunity.save()
|
||||
Membership(
|
||||
user=comunity,
|
||||
|
@ -858,18 +857,6 @@ Welcome to the wiki page!
|
|||
start_date=timezone.now(),
|
||||
role=settings.SITH_CLUB_ROLES_ID["Board member"],
|
||||
).save()
|
||||
# Adding user tutu
|
||||
tutu = User(
|
||||
username="tutu",
|
||||
last_name="Tu",
|
||||
first_name="Tu",
|
||||
email="tutu@git.an",
|
||||
date_of_birth="1942-06-12",
|
||||
)
|
||||
tutu.set_password("plop")
|
||||
tutu.save()
|
||||
tutu.groups.set([settings.SITH_GROUP_PEDAGOGY_ADMIN_ID])
|
||||
tutu.save()
|
||||
|
||||
# Adding subscription for sli
|
||||
s = Subscription(
|
||||
|
@ -908,18 +895,6 @@ Welcome to the wiki page!
|
|||
start=s.subscription_start,
|
||||
)
|
||||
s.save()
|
||||
# Tutu
|
||||
s = Subscription(
|
||||
member=tutu,
|
||||
subscription_type=default_subscription,
|
||||
payment_method=settings.SITH_SUBSCRIPTION_PAYMENT_METHOD[0][0],
|
||||
)
|
||||
s.subscription_start = s.compute_start()
|
||||
s.subscription_end = s.compute_end(
|
||||
duration=settings.SITH_SUBSCRIPTIONS[s.subscription_type]["duration"],
|
||||
start=s.subscription_start,
|
||||
)
|
||||
s.save()
|
||||
|
||||
Selling(
|
||||
label=dcons.name,
|
||||
|
@ -1102,35 +1077,3 @@ Welcome to the wiki page!
|
|||
start_date=friday + timedelta(hours=24 * 7 * i),
|
||||
end_date=friday + timedelta(hours=24 * 7 * i + 8),
|
||||
).save()
|
||||
|
||||
# Create som data for pedagogy
|
||||
|
||||
UV(
|
||||
code="PA00",
|
||||
author=User.objects.get(id=0),
|
||||
credit_type=settings.SITH_PEDAGOGY_UV_TYPE[3][0],
|
||||
manager="Laurent HEYBERGER",
|
||||
semester=settings.SITH_PEDAGOGY_UV_SEMESTER[3][0],
|
||||
language=settings.SITH_PEDAGOGY_UV_LANGUAGE[0][0],
|
||||
department=settings.SITH_PROFILE_DEPARTMENTS[-2][0],
|
||||
credits=5,
|
||||
title="Participation dans une association étudiante",
|
||||
objectives="* Permettre aux étudiants de réaliser, pendant un semestre, un projet culturel ou associatif et de le valoriser.",
|
||||
program="""* Semestre précédent proposition d'un projet et d'un cahier des charges
|
||||
* Evaluation par un jury de six membres
|
||||
* Si accord réalisation dans le cadre de l'UV
|
||||
* Compte-rendu de l'expérience
|
||||
* Présentation""",
|
||||
skills="""* Gérer un projet associatif ou une action éducative en autonomie:
|
||||
* en produisant un cahier des charges qui -définit clairement le contexte du projet personnel -pose les jalons de ce projet -estime de manière réaliste les moyens et objectifs du projet -définit exactement les livrables attendus
|
||||
* en étant capable de respecter ce cahier des charges ou, le cas échéant, de réviser le cahier des charges de manière argumentée.
|
||||
* Relater son expérience dans un rapport:
|
||||
* qui permettra à d'autres étudiants de poursuivre les actions engagées
|
||||
* qui montre la capacité à s'auto-évaluer et à adopter une distance critique sur son action.""",
|
||||
key_concepts="""* Autonomie
|
||||
* Responsabilité
|
||||
* Cahier des charges
|
||||
* Gestion de projet""",
|
||||
hours_THE=121,
|
||||
hours_TE=4,
|
||||
).save()
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
import os
|
||||
import re
|
||||
from mistune import Renderer, InlineGrammar, InlineLexer, Markdown, escape, escape_link
|
||||
from django.urls import reverse
|
||||
from django.core.urlresolvers import reverse
|
||||
|
||||
|
||||
class SithRenderer(Renderer):
|
||||
|
|
|
@ -37,8 +37,8 @@ AnonymousUser = getattr(importlib.import_module(module), klass)
|
|||
def get_cached_user(request):
|
||||
if not hasattr(request, "_cached_user"):
|
||||
user = get_user(request)
|
||||
if user.is_anonymous:
|
||||
user = AnonymousUser()
|
||||
if user.is_anonymous():
|
||||
user = AnonymousUser(request)
|
||||
|
||||
request._cached_user = user
|
||||
|
||||
|
|
|
@ -8,7 +8,6 @@ import django.core.validators
|
|||
import core.models
|
||||
import phonenumber_field.modelfields
|
||||
from django.conf import settings
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -277,7 +276,6 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"group_ptr",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
primary_key=True,
|
||||
parent_link=True,
|
||||
serialize=False,
|
||||
|
@ -331,7 +329,6 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"owner_group",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
default=1,
|
||||
related_name="owned_page",
|
||||
verbose_name="owner group",
|
||||
|
@ -393,19 +390,10 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"author",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
related_name="page_rev",
|
||||
),
|
||||
),
|
||||
(
|
||||
"page",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="core.Page",
|
||||
related_name="revisions",
|
||||
to=settings.AUTH_USER_MODEL, related_name="page_rev"
|
||||
),
|
||||
),
|
||||
("page", models.ForeignKey(to="core.Page", related_name="revisions")),
|
||||
],
|
||||
options={"ordering": ["date"]},
|
||||
),
|
||||
|
@ -432,9 +420,7 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"user",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
related_name="preferences",
|
||||
to=settings.AUTH_USER_MODEL, related_name="preferences"
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -483,7 +469,6 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"owner",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
verbose_name="owner",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
related_name="owned_files",
|
||||
|
@ -492,7 +477,6 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"parent",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
null=True,
|
||||
related_name="children",
|
||||
verbose_name="parent",
|
||||
|
@ -528,7 +512,6 @@ class Migration(migrations.Migration):
|
|||
model_name="user",
|
||||
name="home",
|
||||
field=models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
blank=True,
|
||||
null=True,
|
||||
related_name="home_of",
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -22,7 +21,6 @@ class Migration(migrations.Migration):
|
|||
model_name="page",
|
||||
name="lock_user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
verbose_name="lock user",
|
||||
default=None,
|
||||
blank=True,
|
||||
|
|
|
@ -4,7 +4,6 @@ from __future__ import unicode_literals
|
|||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
import django.utils.timezone
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -49,9 +48,7 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="notifications",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
related_name="notifications", to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -15,7 +14,6 @@ class Migration(migrations.Migration):
|
|||
model_name="sithfile",
|
||||
name="moderator",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="moderated_files",
|
||||
verbose_name="owner",
|
||||
default=0,
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -15,7 +14,6 @@ class Migration(migrations.Migration):
|
|||
model_name="sithfile",
|
||||
name="moderator",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="moderated_files",
|
||||
blank=True,
|
||||
null=True,
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -36,9 +35,7 @@ class Migration(migrations.Migration):
|
|||
model_name="preferences",
|
||||
name="user",
|
||||
field=models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="_preferences",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
related_name="_preferences", to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
]
|
||||
|
|
|
@ -4,7 +4,6 @@ from __future__ import unicode_literals
|
|||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
import django.utils.timezone
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -34,9 +33,7 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
related_name="gifts",
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
related_name="gifts", to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
@ -3,7 +3,6 @@ from __future__ import unicode_literals
|
|||
|
||||
from django.db import migrations, models
|
||||
import core.models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -15,7 +14,6 @@ class Migration(migrations.Migration):
|
|||
model_name="page",
|
||||
name="owner_group",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
verbose_name="owner group",
|
||||
default=core.models.Page.get_default_owner_group,
|
||||
related_name="owned_page",
|
||||
|
|
|
@ -1,40 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.20 on 2019-07-04 13:00
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("core", "0029_auto_20180426_2013")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="type",
|
||||
field=models.CharField(
|
||||
choices=[
|
||||
("POSTER_MODERATION", "A new poster needs to be moderated"),
|
||||
("MAILING_MODERATION", "A new mailing list needs to be moderated"),
|
||||
(
|
||||
"PEDAGOGY_MODERATION",
|
||||
"A new pedagogy comment has been signaled for moderation",
|
||||
),
|
||||
("NEWS_MODERATION", "There are %s fresh news to be moderated"),
|
||||
("FILE_MODERATION", "New files to be moderated"),
|
||||
(
|
||||
"SAS_MODERATION",
|
||||
"There are %s pictures to be moderated in the SAS",
|
||||
),
|
||||
("NEW_PICTURES", "You've been identified on some pictures"),
|
||||
("REFILLING", "You just refilled of %s €"),
|
||||
("SELLING", "You just bought %s"),
|
||||
("GENERIC", "You have a notification"),
|
||||
],
|
||||
default="GENERIC",
|
||||
max_length=32,
|
||||
verbose_name="type",
|
||||
),
|
||||
)
|
||||
]
|
|
@ -1,27 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.24 on 2019-09-06 14:15
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("core", "0030_auto_20190704_1500")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="sithfile",
|
||||
name="is_folder",
|
||||
field=models.BooleanField(
|
||||
db_index=True, default=True, verbose_name="is folder"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="sithfile",
|
||||
name="is_in_sas",
|
||||
field=models.BooleanField(
|
||||
db_index=True, default=False, verbose_name="is in the SAS"
|
||||
),
|
||||
),
|
||||
]
|
|
@ -1,20 +0,0 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.11.24 on 2019-09-08 22:43
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("core", "0031_auto_20190906_1615")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name="notification",
|
||||
name="viewed",
|
||||
field=models.BooleanField(
|
||||
db_index=True, default=False, verbose_name="viewed"
|
||||
),
|
||||
)
|
||||
]
|
|
@ -1,27 +0,0 @@
|
|||
# Generated by Django 2.2.6 on 2019-10-05 22:49
|
||||
|
||||
import django.contrib.auth.models
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [("core", "0032_auto_20190909_0043")]
|
||||
|
||||
operations = [
|
||||
migrations.AlterModelOptions(
|
||||
name="page",
|
||||
options={
|
||||
"permissions": (
|
||||
(
|
||||
"change_prop_page",
|
||||
"Can change the page's properties (groups, ...)",
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
migrations.AlterModelManagers(
|
||||
name="group",
|
||||
managers=[("objects", django.contrib.auth.models.GroupManager())],
|
||||
),
|
||||
]
|
|
@ -38,7 +38,7 @@ from django.utils.translation import ugettext_lazy as _
|
|||
from django.utils import timezone
|
||||
from django.core import validators
|
||||
from django.core.exceptions import ValidationError, PermissionDenied
|
||||
from django.urls import reverse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.contrib.staticfiles.storage import staticfiles_storage
|
||||
|
@ -659,7 +659,7 @@ class User(AbstractBaseUser):
|
|||
|
||||
|
||||
class AnonymousUser(AuthAnonymousUser):
|
||||
def __init__(self):
|
||||
def __init__(self, request):
|
||||
super(AnonymousUser, self).__init__()
|
||||
|
||||
@property
|
||||
|
@ -670,10 +670,6 @@ class AnonymousUser(AuthAnonymousUser):
|
|||
def was_subscribed(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def is_subscribed(self):
|
||||
return False
|
||||
|
||||
@property
|
||||
def subscribed(self):
|
||||
return False
|
||||
|
@ -743,9 +739,7 @@ class AnonymousUser(AuthAnonymousUser):
|
|||
|
||||
|
||||
class Preferences(models.Model):
|
||||
user = models.OneToOneField(
|
||||
User, related_name="_preferences", on_delete=models.CASCADE
|
||||
)
|
||||
user = models.OneToOneField(User, related_name="_preferences")
|
||||
receive_weekmail = models.BooleanField(
|
||||
_("do you want to receive the weekmail"), default=False
|
||||
)
|
||||
|
@ -779,12 +773,7 @@ def get_thumbnail_directory(instance, filename):
|
|||
class SithFile(models.Model):
|
||||
name = models.CharField(_("file name"), max_length=256, blank=False)
|
||||
parent = models.ForeignKey(
|
||||
"self",
|
||||
related_name="children",
|
||||
verbose_name=_("parent"),
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.CASCADE,
|
||||
"self", related_name="children", verbose_name=_("parent"), null=True, blank=True
|
||||
)
|
||||
file = models.FileField(
|
||||
upload_to=get_directory,
|
||||
|
@ -807,19 +796,14 @@ class SithFile(models.Model):
|
|||
null=True,
|
||||
blank=True,
|
||||
)
|
||||
owner = models.ForeignKey(
|
||||
User,
|
||||
related_name="owned_files",
|
||||
verbose_name=_("owner"),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
owner = models.ForeignKey(User, related_name="owned_files", verbose_name=_("owner"))
|
||||
edit_groups = models.ManyToManyField(
|
||||
Group, related_name="editable_files", verbose_name=_("edit group"), blank=True
|
||||
)
|
||||
view_groups = models.ManyToManyField(
|
||||
Group, related_name="viewable_files", verbose_name=_("view group"), blank=True
|
||||
)
|
||||
is_folder = models.BooleanField(_("is folder"), default=True, db_index=True)
|
||||
is_folder = models.BooleanField(_("is folder"), default=True)
|
||||
mime_type = models.CharField(_("mime type"), max_length=30)
|
||||
size = models.IntegerField(_("size"), default=0)
|
||||
date = models.DateTimeField(_("date"), default=timezone.now)
|
||||
|
@ -830,11 +814,10 @@ class SithFile(models.Model):
|
|||
verbose_name=_("owner"),
|
||||
null=True,
|
||||
blank=True,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
asked_for_removal = models.BooleanField(_("asked for removal"), default=False)
|
||||
is_in_sas = models.BooleanField(
|
||||
_("is in the SAS"), default=False, db_index=True
|
||||
_("is in the SAS"), default=False
|
||||
) # Allows to query this flag, updated at each call to save()
|
||||
|
||||
class Meta:
|
||||
|
@ -948,8 +931,8 @@ class SithFile(models.Model):
|
|||
def copy_rights(self):
|
||||
"""Copy, if possible, the rights of the parent folder"""
|
||||
if self.parent is not None:
|
||||
self.edit_groups.set(self.parent.edit_groups.all())
|
||||
self.view_groups.set(self.parent.view_groups.all())
|
||||
self.edit_groups = self.parent.edit_groups.all()
|
||||
self.view_groups = self.parent.view_groups.all()
|
||||
self.save()
|
||||
|
||||
def move_to(self, parent):
|
||||
|
@ -1146,7 +1129,6 @@ class Page(models.Model):
|
|||
related_name="owned_page",
|
||||
verbose_name=_("owner group"),
|
||||
default=get_default_owner_group,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
edit_groups = models.ManyToManyField(
|
||||
Group, related_name="editable_page", verbose_name=_("edit group"), blank=True
|
||||
|
@ -1161,7 +1143,6 @@ class Page(models.Model):
|
|||
blank=True,
|
||||
null=True,
|
||||
default=None,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
lock_timeout = models.DateTimeField(
|
||||
_("lock_timeout"), null=True, blank=True, default=None
|
||||
|
@ -1171,6 +1152,7 @@ class Page(models.Model):
|
|||
unique_together = ("name", "parent")
|
||||
permissions = (
|
||||
("change_prop_page", "Can change the page's properties (groups, ...)"),
|
||||
("view_page", "Can view the page"),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
|
@ -1361,8 +1343,8 @@ class PageRev(models.Model):
|
|||
title = models.CharField(_("page title"), max_length=255, blank=True)
|
||||
content = models.TextField(_("page content"), blank=True)
|
||||
date = models.DateTimeField(_("date"), auto_now=True)
|
||||
author = models.ForeignKey(User, related_name="page_rev", on_delete=models.CASCADE)
|
||||
page = models.ForeignKey(Page, related_name="revisions", on_delete=models.CASCADE)
|
||||
author = models.ForeignKey(User, related_name="page_rev")
|
||||
page = models.ForeignKey(Page, related_name="revisions")
|
||||
|
||||
class Meta:
|
||||
ordering = ["date"]
|
||||
|
@ -1400,16 +1382,14 @@ class PageRev(models.Model):
|
|||
|
||||
|
||||
class Notification(models.Model):
|
||||
user = models.ForeignKey(
|
||||
User, related_name="notifications", on_delete=models.CASCADE
|
||||
)
|
||||
user = models.ForeignKey(User, related_name="notifications")
|
||||
url = models.CharField(_("url"), max_length=255)
|
||||
param = models.CharField(_("param"), max_length=128, default="")
|
||||
type = models.CharField(
|
||||
_("type"), max_length=32, choices=settings.SITH_NOTIFICATIONS, default="GENERIC"
|
||||
)
|
||||
date = models.DateTimeField(_("date"), default=timezone.now)
|
||||
viewed = models.BooleanField(_("viewed"), default=False, db_index=True)
|
||||
viewed = models.BooleanField(_("viewed"), default=False)
|
||||
|
||||
def __str__(self):
|
||||
if self.param:
|
||||
|
@ -1438,7 +1418,7 @@ class Notification(models.Model):
|
|||
class Gift(models.Model):
|
||||
label = models.CharField(_("label"), max_length=255)
|
||||
date = models.DateTimeField(_("date"), default=timezone.now)
|
||||
user = models.ForeignKey(User, related_name="gifts", on_delete=models.CASCADE)
|
||||
user = models.ForeignKey(User, related_name="gifts")
|
||||
|
||||
def __str__(self):
|
||||
return "%s - %s" % (self.translated_label, self.date.strftime("%d %b %Y"))
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 70 KiB |
Binary file not shown.
Before Width: | Height: | Size: 48 KiB |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,782 @@
|
|||
/**
|
||||
* @author zhixin wen <wenzhixin2010@gmail.com>
|
||||
* @version 1.2.1
|
||||
*
|
||||
* http://wenzhixin.net.cn/p/multiple-select/
|
||||
*/
|
||||
|
||||
(function ($) {
|
||||
|
||||
'use strict';
|
||||
|
||||
// it only does '%s', and return '' when arguments are undefined
|
||||
var sprintf = function (str) {
|
||||
var args = arguments,
|
||||
flag = true,
|
||||
i = 1;
|
||||
|
||||
str = str.replace(/%s/g, function () {
|
||||
var arg = args[i++];
|
||||
|
||||
if (typeof arg === 'undefined') {
|
||||
flag = false;
|
||||
return '';
|
||||
}
|
||||
return arg;
|
||||
});
|
||||
return flag ? str : '';
|
||||
};
|
||||
|
||||
var removeDiacritics = function (str) {
|
||||
var defaultDiacriticsRemovalMap = [
|
||||
{'base':'A', 'letters':/[\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F]/g},
|
||||
{'base':'AA','letters':/[\uA732]/g},
|
||||
{'base':'AE','letters':/[\u00C6\u01FC\u01E2]/g},
|
||||
{'base':'AO','letters':/[\uA734]/g},
|
||||
{'base':'AU','letters':/[\uA736]/g},
|
||||
{'base':'AV','letters':/[\uA738\uA73A]/g},
|
||||
{'base':'AY','letters':/[\uA73C]/g},
|
||||
{'base':'B', 'letters':/[\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181]/g},
|
||||
{'base':'C', 'letters':/[\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E]/g},
|
||||
{'base':'D', 'letters':/[\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779]/g},
|
||||
{'base':'DZ','letters':/[\u01F1\u01C4]/g},
|
||||
{'base':'Dz','letters':/[\u01F2\u01C5]/g},
|
||||
{'base':'E', 'letters':/[\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E]/g},
|
||||
{'base':'F', 'letters':/[\u0046\u24BB\uFF26\u1E1E\u0191\uA77B]/g},
|
||||
{'base':'G', 'letters':/[\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E]/g},
|
||||
{'base':'H', 'letters':/[\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D]/g},
|
||||
{'base':'I', 'letters':/[\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197]/g},
|
||||
{'base':'J', 'letters':/[\u004A\u24BF\uFF2A\u0134\u0248]/g},
|
||||
{'base':'K', 'letters':/[\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2]/g},
|
||||
{'base':'L', 'letters':/[\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780]/g},
|
||||
{'base':'LJ','letters':/[\u01C7]/g},
|
||||
{'base':'Lj','letters':/[\u01C8]/g},
|
||||
{'base':'M', 'letters':/[\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C]/g},
|
||||
{'base':'N', 'letters':/[\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4]/g},
|
||||
{'base':'NJ','letters':/[\u01CA]/g},
|
||||
{'base':'Nj','letters':/[\u01CB]/g},
|
||||
{'base':'O', 'letters':/[\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C]/g},
|
||||
{'base':'OI','letters':/[\u01A2]/g},
|
||||
{'base':'OO','letters':/[\uA74E]/g},
|
||||
{'base':'OU','letters':/[\u0222]/g},
|
||||
{'base':'P', 'letters':/[\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754]/g},
|
||||
{'base':'Q', 'letters':/[\u0051\u24C6\uFF31\uA756\uA758\u024A]/g},
|
||||
{'base':'R', 'letters':/[\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782]/g},
|
||||
{'base':'S', 'letters':/[\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784]/g},
|
||||
{'base':'T', 'letters':/[\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786]/g},
|
||||
{'base':'TZ','letters':/[\uA728]/g},
|
||||
{'base':'U', 'letters':/[\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244]/g},
|
||||
{'base':'V', 'letters':/[\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245]/g},
|
||||
{'base':'VY','letters':/[\uA760]/g},
|
||||
{'base':'W', 'letters':/[\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72]/g},
|
||||
{'base':'X', 'letters':/[\u0058\u24CD\uFF38\u1E8A\u1E8C]/g},
|
||||
{'base':'Y', 'letters':/[\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE]/g},
|
||||
{'base':'Z', 'letters':/[\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762]/g},
|
||||
{'base':'a', 'letters':/[\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250]/g},
|
||||
{'base':'aa','letters':/[\uA733]/g},
|
||||
{'base':'ae','letters':/[\u00E6\u01FD\u01E3]/g},
|
||||
{'base':'ao','letters':/[\uA735]/g},
|
||||
{'base':'au','letters':/[\uA737]/g},
|
||||
{'base':'av','letters':/[\uA739\uA73B]/g},
|
||||
{'base':'ay','letters':/[\uA73D]/g},
|
||||
{'base':'b', 'letters':/[\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253]/g},
|
||||
{'base':'c', 'letters':/[\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184]/g},
|
||||
{'base':'d', 'letters':/[\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A]/g},
|
||||
{'base':'dz','letters':/[\u01F3\u01C6]/g},
|
||||
{'base':'e', 'letters':/[\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD]/g},
|
||||
{'base':'f', 'letters':/[\u0066\u24D5\uFF46\u1E1F\u0192\uA77C]/g},
|
||||
{'base':'g', 'letters':/[\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F]/g},
|
||||
{'base':'h', 'letters':/[\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265]/g},
|
||||
{'base':'hv','letters':/[\u0195]/g},
|
||||
{'base':'i', 'letters':/[\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131]/g},
|
||||
{'base':'j', 'letters':/[\u006A\u24D9\uFF4A\u0135\u01F0\u0249]/g},
|
||||
{'base':'k', 'letters':/[\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3]/g},
|
||||
{'base':'l', 'letters':/[\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747]/g},
|
||||
{'base':'lj','letters':/[\u01C9]/g},
|
||||
{'base':'m', 'letters':/[\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F]/g},
|
||||
{'base':'n', 'letters':/[\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5]/g},
|
||||
{'base':'nj','letters':/[\u01CC]/g},
|
||||
{'base':'o', 'letters':/[\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275]/g},
|
||||
{'base':'oi','letters':/[\u01A3]/g},
|
||||
{'base':'ou','letters':/[\u0223]/g},
|
||||
{'base':'oo','letters':/[\uA74F]/g},
|
||||
{'base':'p','letters':/[\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755]/g},
|
||||
{'base':'q','letters':/[\u0071\u24E0\uFF51\u024B\uA757\uA759]/g},
|
||||
{'base':'r','letters':/[\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783]/g},
|
||||
{'base':'s','letters':/[\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B]/g},
|
||||
{'base':'t','letters':/[\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787]/g},
|
||||
{'base':'tz','letters':/[\uA729]/g},
|
||||
{'base':'u','letters':/[\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289]/g},
|
||||
{'base':'v','letters':/[\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C]/g},
|
||||
{'base':'vy','letters':/[\uA761]/g},
|
||||
{'base':'w','letters':/[\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73]/g},
|
||||
{'base':'x','letters':/[\u0078\u24E7\uFF58\u1E8B\u1E8D]/g},
|
||||
{'base':'y','letters':/[\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF]/g},
|
||||
{'base':'z','letters':/[\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763]/g}
|
||||
];
|
||||
|
||||
for (var i = 0; i < defaultDiacriticsRemovalMap.length; i++) {
|
||||
str = str.replace(defaultDiacriticsRemovalMap[i].letters, defaultDiacriticsRemovalMap[i].base);
|
||||
}
|
||||
|
||||
return str;
|
||||
|
||||
};
|
||||
|
||||
function MultipleSelect($el, options) {
|
||||
var that = this,
|
||||
name = $el.attr('name') || options.name || '';
|
||||
|
||||
this.options = options;
|
||||
|
||||
// hide select element
|
||||
this.$el = $el.hide();
|
||||
|
||||
// label element
|
||||
this.$label = this.$el.closest('label');
|
||||
if (this.$label.length === 0 && this.$el.attr('id')) {
|
||||
this.$label = $(sprintf('label[for="%s"]', this.$el.attr('id').replace(/:/g, '\\:')));
|
||||
}
|
||||
|
||||
// restore class and title from select element
|
||||
this.$parent = $(sprintf(
|
||||
'<div class="ms-parent %s" %s/>',
|
||||
$el.attr('class') || '',
|
||||
sprintf('title="%s"', $el.attr('title'))));
|
||||
|
||||
// add placeholder to choice button
|
||||
this.$choice = $(sprintf([
|
||||
'<button type="button" class="ms-choice">',
|
||||
'<span class="placeholder">%s</span>',
|
||||
'<div></div>',
|
||||
'</button>'
|
||||
].join(''),
|
||||
this.options.placeholder));
|
||||
|
||||
// default position is bottom
|
||||
this.$drop = $(sprintf('<div class="ms-drop %s"%s></div>',
|
||||
this.options.position,
|
||||
sprintf(' style="width: %s"', this.options.dropWidth)));
|
||||
|
||||
this.$el.after(this.$parent);
|
||||
this.$parent.append(this.$choice);
|
||||
this.$parent.append(this.$drop);
|
||||
|
||||
if (this.$el.prop('disabled')) {
|
||||
this.$choice.addClass('disabled');
|
||||
}
|
||||
this.$parent.css('width',
|
||||
this.options.width ||
|
||||
this.$el.css('width') ||
|
||||
this.$el.outerWidth() + 20);
|
||||
|
||||
this.selectAllName = 'data-name="selectAll' + name + '"';
|
||||
this.selectGroupName = 'data-name="selectGroup' + name + '"';
|
||||
this.selectItemName = 'data-name="selectItem' + name + '"';
|
||||
|
||||
if (!this.options.keepOpen) {
|
||||
$(document).click(function (e) {
|
||||
if ($(e.target)[0] === that.$choice[0] ||
|
||||
$(e.target).parents('.ms-choice')[0] === that.$choice[0]) {
|
||||
return;
|
||||
}
|
||||
if (($(e.target)[0] === that.$drop[0] ||
|
||||
$(e.target).parents('.ms-drop')[0] !== that.$drop[0] && e.target !== $el[0]) &&
|
||||
that.options.isOpen) {
|
||||
that.close();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
MultipleSelect.prototype = {
|
||||
constructor: MultipleSelect,
|
||||
|
||||
init: function () {
|
||||
var that = this,
|
||||
$ul = $('<ul></ul>');
|
||||
|
||||
this.$drop.html('');
|
||||
|
||||
if (this.options.filter) {
|
||||
this.$drop.append([
|
||||
'<div class="ms-search">',
|
||||
'<input type="text" autocomplete="off" autocorrect="off" autocapitilize="off" spellcheck="false">',
|
||||
'</div>'].join('')
|
||||
);
|
||||
}
|
||||
|
||||
if (this.options.selectAll && !this.options.single) {
|
||||
$ul.append([
|
||||
'<li class="ms-select-all">',
|
||||
'<label>',
|
||||
sprintf('<input type="checkbox" %s /> ', this.selectAllName),
|
||||
this.options.selectAllDelimiter[0],
|
||||
this.options.selectAllText,
|
||||
this.options.selectAllDelimiter[1],
|
||||
'</label>',
|
||||
'</li>'
|
||||
].join(''));
|
||||
}
|
||||
|
||||
$.each(this.$el.children(), function (i, elm) {
|
||||
$ul.append(that.optionToHtml(i, elm));
|
||||
});
|
||||
$ul.append(sprintf('<li class="ms-no-results">%s</li>', this.options.noMatchesFound));
|
||||
this.$drop.append($ul);
|
||||
|
||||
this.$drop.find('ul').css('max-height', this.options.maxHeight + 'px');
|
||||
this.$drop.find('.multiple').css('width', this.options.multipleWidth + 'px');
|
||||
|
||||
this.$searchInput = this.$drop.find('.ms-search input');
|
||||
this.$selectAll = this.$drop.find('input[' + this.selectAllName + ']');
|
||||
this.$selectGroups = this.$drop.find('input[' + this.selectGroupName + ']');
|
||||
this.$selectItems = this.$drop.find('input[' + this.selectItemName + ']:enabled');
|
||||
this.$disableItems = this.$drop.find('input[' + this.selectItemName + ']:disabled');
|
||||
this.$noResults = this.$drop.find('.ms-no-results');
|
||||
|
||||
this.events();
|
||||
this.updateSelectAll(true);
|
||||
this.update(true);
|
||||
|
||||
if (this.options.isOpen) {
|
||||
this.open();
|
||||
}
|
||||
},
|
||||
|
||||
optionToHtml: function (i, elm, group, groupDisabled) {
|
||||
var that = this,
|
||||
$elm = $(elm),
|
||||
classes = $elm.attr('class') || '',
|
||||
title = sprintf('title="%s"', $elm.attr('title')),
|
||||
multiple = this.options.multiple ? 'multiple' : '',
|
||||
disabled,
|
||||
type = this.options.single ? 'radio' : 'checkbox';
|
||||
|
||||
if ($elm.is('option')) {
|
||||
var value = $elm.val(),
|
||||
text = that.options.textTemplate($elm),
|
||||
selected = $elm.prop('selected'),
|
||||
style = sprintf('style="%s"', this.options.styler(value)),
|
||||
$el;
|
||||
|
||||
disabled = groupDisabled || $elm.prop('disabled');
|
||||
|
||||
$el = $([
|
||||
sprintf('<li class="%s %s" %s %s>', multiple, classes, title, style),
|
||||
sprintf('<label class="%s">', disabled ? 'disabled' : ''),
|
||||
sprintf('<input type="%s" %s%s%s%s>',
|
||||
type, this.selectItemName,
|
||||
selected ? ' checked="checked"' : '',
|
||||
disabled ? ' disabled="disabled"' : '',
|
||||
sprintf(' data-group="%s"', group)),
|
||||
sprintf('<span>%s</span>', text),
|
||||
'</label>',
|
||||
'</li>'
|
||||
].join(''));
|
||||
$el.find('input').val(value);
|
||||
return $el;
|
||||
}
|
||||
if ($elm.is('optgroup')) {
|
||||
var label = that.options.labelTemplate($elm),
|
||||
$group = $('<div/>');
|
||||
|
||||
group = 'group_' + i;
|
||||
disabled = $elm.prop('disabled');
|
||||
|
||||
$group.append([
|
||||
'<li class="group">',
|
||||
sprintf('<label class="optgroup %s" data-group="%s">', disabled ? 'disabled' : '', group),
|
||||
this.options.hideOptgroupCheckboxes || this.options.single ? '' :
|
||||
sprintf('<input type="checkbox" %s %s>',
|
||||
this.selectGroupName, disabled ? 'disabled="disabled"' : ''),
|
||||
label,
|
||||
'</label>',
|
||||
'</li>'
|
||||
].join(''));
|
||||
|
||||
$.each($elm.children(), function (i, elm) {
|
||||
$group.append(that.optionToHtml(i, elm, group, disabled));
|
||||
});
|
||||
return $group.html();
|
||||
}
|
||||
},
|
||||
|
||||
events: function () {
|
||||
var that = this,
|
||||
toggleOpen = function (e) {
|
||||
e.preventDefault();
|
||||
that[that.options.isOpen ? 'close' : 'open']();
|
||||
};
|
||||
|
||||
if (this.$label) {
|
||||
this.$label.off('click').on('click', function (e) {
|
||||
if (e.target.nodeName.toLowerCase() !== 'label' || e.target !== this) {
|
||||
return;
|
||||
}
|
||||
toggleOpen(e);
|
||||
if (!that.options.filter || !that.options.isOpen) {
|
||||
that.focus();
|
||||
}
|
||||
e.stopPropagation(); // Causes lost focus otherwise
|
||||
});
|
||||
}
|
||||
|
||||
this.$choice.off('click').on('click', toggleOpen)
|
||||
.off('focus').on('focus', this.options.onFocus)
|
||||
.off('blur').on('blur', this.options.onBlur);
|
||||
|
||||
this.$parent.off('keydown').on('keydown', function (e) {
|
||||
switch (e.which) {
|
||||
case 27: // esc key
|
||||
that.close();
|
||||
that.$choice.focus();
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
this.$searchInput.off('keydown').on('keydown',function (e) {
|
||||
// Ensure shift-tab causes lost focus from filter as with clicking away
|
||||
if (e.keyCode === 9 && e.shiftKey) {
|
||||
that.close();
|
||||
}
|
||||
}).off('keyup').on('keyup', function (e) {
|
||||
// enter or space
|
||||
// Avoid selecting/deselecting if no choices made
|
||||
if (that.options.filterAcceptOnEnter && (e.which === 13 || e.which == 32) && that.$searchInput.val()) {
|
||||
that.$selectAll.click();
|
||||
that.close();
|
||||
that.focus();
|
||||
return;
|
||||
}
|
||||
that.filter();
|
||||
});
|
||||
|
||||
this.$selectAll.off('click').on('click', function () {
|
||||
var checked = $(this).prop('checked'),
|
||||
$items = that.$selectItems.filter(':visible');
|
||||
|
||||
if ($items.length === that.$selectItems.length) {
|
||||
that[checked ? 'checkAll' : 'uncheckAll']();
|
||||
} else { // when the filter option is true
|
||||
that.$selectGroups.prop('checked', checked);
|
||||
$items.prop('checked', checked);
|
||||
that.options[checked ? 'onCheckAll' : 'onUncheckAll']();
|
||||
that.update();
|
||||
}
|
||||
});
|
||||
this.$selectGroups.off('click').on('click', function () {
|
||||
var group = $(this).parent().attr('data-group'),
|
||||
$items = that.$selectItems.filter(':visible'),
|
||||
$children = $items.filter(sprintf('[data-group="%s"]', group)),
|
||||
checked = $children.length !== $children.filter(':checked').length;
|
||||
|
||||
$children.prop('checked', checked);
|
||||
that.updateSelectAll();
|
||||
that.update();
|
||||
that.options.onOptgroupClick({
|
||||
label: $(this).parent().text(),
|
||||
checked: checked,
|
||||
children: $children.get(),
|
||||
instance: that
|
||||
});
|
||||
});
|
||||
this.$selectItems.off('click').on('click', function () {
|
||||
that.updateSelectAll();
|
||||
that.update();
|
||||
that.updateOptGroupSelect();
|
||||
that.options.onClick({
|
||||
label: $(this).parent().text(),
|
||||
value: $(this).val(),
|
||||
checked: $(this).prop('checked'),
|
||||
instance: that
|
||||
});
|
||||
|
||||
if (that.options.single && that.options.isOpen && !that.options.keepOpen) {
|
||||
that.close();
|
||||
}
|
||||
|
||||
if (that.options.single) {
|
||||
var clickedVal = $(this).val();
|
||||
that.$selectItems.filter(function() {
|
||||
return $(this).val() !== clickedVal;
|
||||
}).each(function() {
|
||||
$(this).prop('checked', false);
|
||||
});
|
||||
that.update();
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
open: function () {
|
||||
if (this.$choice.hasClass('disabled')) {
|
||||
return;
|
||||
}
|
||||
this.options.isOpen = true;
|
||||
this.$choice.find('>div').addClass('open');
|
||||
this.$drop[this.animateMethod('show')]();
|
||||
|
||||
// fix filter bug: no results show
|
||||
this.$selectAll.parent().show();
|
||||
this.$noResults.hide();
|
||||
|
||||
// Fix #77: 'All selected' when no options
|
||||
if (!this.$el.children().length) {
|
||||
this.$selectAll.parent().hide();
|
||||
this.$noResults.show();
|
||||
}
|
||||
|
||||
if (this.options.container) {
|
||||
var offset = this.$drop.offset();
|
||||
this.$drop.appendTo($(this.options.container));
|
||||
this.$drop.offset({
|
||||
top: offset.top,
|
||||
left: offset.left
|
||||
});
|
||||
}
|
||||
|
||||
if (this.options.filter) {
|
||||
this.$searchInput.val('');
|
||||
this.$searchInput.focus();
|
||||
this.filter();
|
||||
}
|
||||
this.options.onOpen();
|
||||
},
|
||||
|
||||
close: function () {
|
||||
this.options.isOpen = false;
|
||||
this.$choice.find('>div').removeClass('open');
|
||||
this.$drop[this.animateMethod('hide')]();
|
||||
if (this.options.container) {
|
||||
this.$parent.append(this.$drop);
|
||||
this.$drop.css({
|
||||
'top': 'auto',
|
||||
'left': 'auto'
|
||||
});
|
||||
}
|
||||
this.options.onClose();
|
||||
},
|
||||
|
||||
animateMethod: function (method) {
|
||||
var methods = {
|
||||
show: {
|
||||
fade: 'fadeIn',
|
||||
slide: 'slideDown'
|
||||
},
|
||||
hide: {
|
||||
fade: 'fadeOut',
|
||||
slide: 'slideUp'
|
||||
}
|
||||
};
|
||||
|
||||
return methods[method][this.options.animate] || method;
|
||||
},
|
||||
|
||||
update: function (isInit) {
|
||||
var selects = this.options.displayValues ? this.getSelects() : this.getSelects('text'),
|
||||
$span = this.$choice.find('>span'),
|
||||
sl = selects.length;
|
||||
|
||||
if (sl === 0) {
|
||||
$span.addClass('placeholder').html(this.options.placeholder);
|
||||
} else if (this.options.allSelected && sl === this.$selectItems.length + this.$disableItems.length) {
|
||||
$span.removeClass('placeholder').html(this.options.allSelected);
|
||||
} else if (this.options.ellipsis && sl > this.options.minimumCountSelected) {
|
||||
$span.removeClass('placeholder').text(selects.slice(0, this.options.minimumCountSelected)
|
||||
.join(this.options.delimiter) + '...');
|
||||
} else if (this.options.countSelected && sl > this.options.minimumCountSelected) {
|
||||
$span.removeClass('placeholder').html(this.options.countSelected
|
||||
.replace('#', selects.length)
|
||||
.replace('%', this.$selectItems.length + this.$disableItems.length));
|
||||
} else {
|
||||
$span.removeClass('placeholder').text(selects.join(this.options.delimiter));
|
||||
}
|
||||
|
||||
if (this.options.addTitle) {
|
||||
$span.prop('title', this.getSelects('text'));
|
||||
}
|
||||
|
||||
// set selects to select
|
||||
this.$el.val(this.getSelects()).trigger('change');
|
||||
|
||||
// add selected class to selected li
|
||||
this.$drop.find('li').removeClass('selected');
|
||||
this.$drop.find('input:checked').each(function () {
|
||||
$(this).parents('li').first().addClass('selected');
|
||||
});
|
||||
|
||||
// trigger <select> change event
|
||||
if (!isInit) {
|
||||
this.$el.trigger('change');
|
||||
}
|
||||
},
|
||||
|
||||
updateSelectAll: function (isInit) {
|
||||
var $items = this.$selectItems;
|
||||
|
||||
if (!isInit) {
|
||||
$items = $items.filter(':visible');
|
||||
}
|
||||
this.$selectAll.prop('checked', $items.length &&
|
||||
$items.length === $items.filter(':checked').length);
|
||||
if (!isInit && this.$selectAll.prop('checked')) {
|
||||
this.options.onCheckAll();
|
||||
}
|
||||
},
|
||||
|
||||
updateOptGroupSelect: function () {
|
||||
var $items = this.$selectItems.filter(':visible');
|
||||
$.each(this.$selectGroups, function (i, val) {
|
||||
var group = $(val).parent().attr('data-group'),
|
||||
$children = $items.filter(sprintf('[data-group="%s"]', group));
|
||||
$(val).prop('checked', $children.length &&
|
||||
$children.length === $children.filter(':checked').length);
|
||||
});
|
||||
},
|
||||
|
||||
//value or text, default: 'value'
|
||||
getSelects: function (type) {
|
||||
var that = this,
|
||||
texts = [],
|
||||
values = [];
|
||||
this.$drop.find(sprintf('input[%s]:checked', this.selectItemName)).each(function () {
|
||||
texts.push($(this).parents('li').first().text());
|
||||
values.push($(this).val());
|
||||
});
|
||||
|
||||
if (type === 'text' && this.$selectGroups.length) {
|
||||
texts = [];
|
||||
this.$selectGroups.each(function () {
|
||||
var html = [],
|
||||
text = $.trim($(this).parent().text()),
|
||||
group = $(this).parent().data('group'),
|
||||
$children = that.$drop.find(sprintf('[%s][data-group="%s"]', that.selectItemName, group)),
|
||||
$selected = $children.filter(':checked');
|
||||
|
||||
if (!$selected.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
html.push('[');
|
||||
html.push(text);
|
||||
if ($children.length > $selected.length) {
|
||||
var list = [];
|
||||
$selected.each(function () {
|
||||
list.push($(this).parent().text());
|
||||
});
|
||||
html.push(': ' + list.join(', '));
|
||||
}
|
||||
html.push(']');
|
||||
texts.push(html.join(''));
|
||||
});
|
||||
}
|
||||
return type === 'text' ? texts : values;
|
||||
},
|
||||
|
||||
setSelects: function (values) {
|
||||
var that = this;
|
||||
this.$selectItems.prop('checked', false);
|
||||
this.$disableItems.prop('checked', false);
|
||||
$.each(values, function (i, value) {
|
||||
that.$selectItems.filter(sprintf('[value="%s"]', value)).prop('checked', true);
|
||||
that.$disableItems.filter(sprintf('[value="%s"]', value)).prop('checked', true);
|
||||
});
|
||||
this.$selectAll.prop('checked', this.$selectItems.length ===
|
||||
this.$selectItems.filter(':checked').length + this.$disableItems.filter(':checked').length);
|
||||
|
||||
$.each(that.$selectGroups, function (i, val) {
|
||||
var group = $(val).parent().attr('data-group'),
|
||||
$children = that.$selectItems.filter('[data-group="' + group + '"]');
|
||||
$(val).prop('checked', $children.length &&
|
||||
$children.length === $children.filter(':checked').length);
|
||||
});
|
||||
|
||||
this.update();
|
||||
},
|
||||
|
||||
enable: function () {
|
||||
this.$choice.removeClass('disabled');
|
||||
},
|
||||
|
||||
disable: function () {
|
||||
this.$choice.addClass('disabled');
|
||||
},
|
||||
|
||||
checkAll: function () {
|
||||
this.$selectItems.prop('checked', true);
|
||||
this.$selectGroups.prop('checked', true);
|
||||
this.$selectAll.prop('checked', true);
|
||||
this.update();
|
||||
this.options.onCheckAll();
|
||||
},
|
||||
|
||||
uncheckAll: function () {
|
||||
this.$selectItems.prop('checked', false);
|
||||
this.$selectGroups.prop('checked', false);
|
||||
this.$selectAll.prop('checked', false);
|
||||
this.update();
|
||||
this.options.onUncheckAll();
|
||||
},
|
||||
|
||||
focus: function () {
|
||||
this.$choice.focus();
|
||||
this.options.onFocus();
|
||||
},
|
||||
|
||||
blur: function () {
|
||||
this.$choice.blur();
|
||||
this.options.onBlur();
|
||||
},
|
||||
|
||||
refresh: function () {
|
||||
this.init();
|
||||
},
|
||||
|
||||
filter: function () {
|
||||
var that = this,
|
||||
text = $.trim(this.$searchInput.val()).toLowerCase();
|
||||
|
||||
if (text.length === 0) {
|
||||
this.$selectAll.parent().show();
|
||||
this.$selectItems.parent().show();
|
||||
this.$disableItems.parent().show();
|
||||
this.$selectGroups.parent().show();
|
||||
this.$noResults.hide();
|
||||
} else {
|
||||
this.$selectItems.each(function () {
|
||||
var $parent = $(this).parent();
|
||||
$parent[removeDiacritics($parent.text().toLowerCase()).indexOf(removeDiacritics(text)) < 0 ? 'hide' : 'show']();
|
||||
});
|
||||
this.$disableItems.parent().hide();
|
||||
this.$selectGroups.each(function () {
|
||||
var $parent = $(this).parent();
|
||||
var group = $parent.attr('data-group'),
|
||||
$items = that.$selectItems.filter(':visible');
|
||||
$parent[$items.filter(sprintf('[data-group="%s"]', group)).length ? 'show' : 'hide']();
|
||||
});
|
||||
|
||||
//Check if no matches found
|
||||
if (this.$selectItems.parent().filter(':visible').length) {
|
||||
this.$selectAll.parent().show();
|
||||
this.$noResults.hide();
|
||||
} else {
|
||||
this.$selectAll.parent().hide();
|
||||
this.$noResults.show();
|
||||
}
|
||||
}
|
||||
this.updateOptGroupSelect();
|
||||
this.updateSelectAll();
|
||||
this.options.onFilter(text);
|
||||
}
|
||||
};
|
||||
|
||||
$.fn.multipleSelect = function () {
|
||||
var option = arguments[0],
|
||||
args = arguments,
|
||||
|
||||
value,
|
||||
allowedMethods = [
|
||||
'getSelects', 'setSelects',
|
||||
'enable', 'disable',
|
||||
'open', 'close',
|
||||
'checkAll', 'uncheckAll',
|
||||
'focus', 'blur',
|
||||
'refresh', 'close'
|
||||
];
|
||||
|
||||
this.each(function () {
|
||||
var $this = $(this),
|
||||
data = $this.data('multipleSelect'),
|
||||
options = $.extend({}, $.fn.multipleSelect.defaults,
|
||||
$this.data(), typeof option === 'object' && option);
|
||||
|
||||
if (!data) {
|
||||
data = new MultipleSelect($this, options);
|
||||
$this.data('multipleSelect', data);
|
||||
}
|
||||
|
||||
if (typeof option === 'string') {
|
||||
if ($.inArray(option, allowedMethods) < 0) {
|
||||
throw 'Unknown method: ' + option;
|
||||
}
|
||||
value = data[option](args[1]);
|
||||
} else {
|
||||
data.init();
|
||||
if (args[1]) {
|
||||
value = data[args[1]].apply(data, [].slice.call(args, 2));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return typeof value !== 'undefined' ? value : this;
|
||||
};
|
||||
|
||||
$.fn.multipleSelect.defaults = {
|
||||
name: '',
|
||||
isOpen: false,
|
||||
placeholder: '',
|
||||
selectAll: true,
|
||||
selectAllDelimiter: ['[', ']'],
|
||||
minimumCountSelected: 3,
|
||||
ellipsis: false,
|
||||
multiple: false,
|
||||
multipleWidth: 80,
|
||||
single: false,
|
||||
filter: false,
|
||||
width: undefined,
|
||||
dropWidth: undefined,
|
||||
maxHeight: 250,
|
||||
container: null,
|
||||
position: 'bottom',
|
||||
keepOpen: false,
|
||||
animate: 'none', // 'none', 'fade', 'slide'
|
||||
displayValues: false,
|
||||
delimiter: ', ',
|
||||
addTitle: false,
|
||||
filterAcceptOnEnter: false,
|
||||
hideOptgroupCheckboxes: false,
|
||||
|
||||
selectAllText: 'Tout sélectionner',
|
||||
allSelected: 'Tout sélectionné',
|
||||
countSelected: '# sur % sélectionnés',
|
||||
noMatchesFound: 'Introuvable',
|
||||
|
||||
styler: function () {
|
||||
return false;
|
||||
},
|
||||
textTemplate: function ($elm) {
|
||||
return $elm.html();
|
||||
},
|
||||
labelTemplate: function ($elm) {
|
||||
return $elm.attr('label');
|
||||
},
|
||||
|
||||
onOpen: function () {
|
||||
return false;
|
||||
},
|
||||
onClose: function () {
|
||||
return false;
|
||||
},
|
||||
onCheckAll: function () {
|
||||
return false;
|
||||
},
|
||||
onUncheckAll: function () {
|
||||
return false;
|
||||
},
|
||||
onFocus: function () {
|
||||
return false;
|
||||
},
|
||||
onBlur: function () {
|
||||
return false;
|
||||
},
|
||||
onOptgroupClick: function () {
|
||||
return false;
|
||||
},
|
||||
onClick: function () {
|
||||
return false;
|
||||
},
|
||||
onFilter: function () {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
})(jQuery);
|
|
@ -38,21 +38,7 @@ $( function() {
|
|||
$("#quick_notif li").click(function () {
|
||||
$(this).hide();
|
||||
})
|
||||
});
|
||||
|
||||
function createQuickNotif(msg) {
|
||||
const el = document.createElement('li')
|
||||
el.textContent = msg
|
||||
el.addEventListener('click', () => el.parentNode.removeChild(el))
|
||||
document.getElementById('quick_notif').appendChild(el)
|
||||
}
|
||||
|
||||
function deleteQuickNotifs() {
|
||||
const el = document.getElementById('quick_notif')
|
||||
while (el.firstChild) {
|
||||
el.removeChild(el.firstChild)
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
function display_notif() {
|
||||
$('#header_notif').toggle().parent().toggleClass("white");
|
||||
|
@ -66,4 +52,4 @@ function display_notif() {
|
|||
// https://docs.djangoproject.com/en/2.0/ref/csrf/#acquiring-the-token-if-csrf-use-sessions-is-true
|
||||
function getCSRFToken() {
|
||||
return $("[name=csrfmiddlewaretoken]").val();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,191 @@
|
|||
/**
|
||||
* @author zhixin wen <wenzhixin2010@gmail.com>
|
||||
*/
|
||||
|
||||
.ms-parent {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ms-choice {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 26px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
cursor: pointer;
|
||||
border: 1px solid #aaa;
|
||||
text-align: left;
|
||||
white-space: nowrap;
|
||||
line-height: 26px;
|
||||
color: #444;
|
||||
text-decoration: none;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.ms-choice.disabled {
|
||||
background-color: #f4f4f4;
|
||||
background-image: none;
|
||||
border: 1px solid #ddd;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.ms-choice > span {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 20px;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: block;
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.ms-choice > span.placeholder {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.ms-choice > div {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
width: 20px;
|
||||
height: 25px;
|
||||
background: url('multiple-select.png') left top no-repeat;
|
||||
}
|
||||
|
||||
.ms-choice > div.open {
|
||||
background: url('multiple-select.png') right top no-repeat;
|
||||
}
|
||||
|
||||
.ms-drop {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
margin-top: -1px;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
background: #fff;
|
||||
color: #000;
|
||||
border: 1px solid #aaa;
|
||||
-webkit-border-radius: 4px;
|
||||
-moz-border-radius: 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.ms-drop.bottom {
|
||||
top: 100%;
|
||||
-webkit-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||
-moz-box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||
box-shadow: 0 4px 5px rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.ms-drop.top {
|
||||
bottom: 100%;
|
||||
-webkit-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||
-moz-box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||
box-shadow: 0 -4px 5px rgba(0, 0, 0, .15);
|
||||
}
|
||||
|
||||
.ms-search {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
min-height: 26px;
|
||||
padding: 4px;
|
||||
position: relative;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
z-index: 10000;
|
||||
}
|
||||
|
||||
.ms-search input {
|
||||
width: 100%;
|
||||
height: auto !important;
|
||||
min-height: 24px;
|
||||
padding: 0 20px 0 5px;
|
||||
margin: 0;
|
||||
outline: 0;
|
||||
font-family: sans-serif;
|
||||
font-size: 1em;
|
||||
border: 1px solid #aaa;
|
||||
-webkit-border-radius: 0;
|
||||
-moz-border-radius: 0;
|
||||
border-radius: 0;
|
||||
-webkit-box-shadow: none;
|
||||
-moz-box-shadow: none;
|
||||
box-shadow: none;
|
||||
background: #fff url('multiple-select.png') no-repeat 100% -22px;
|
||||
background: url('multiple-select.png') no-repeat 100% -22px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
|
||||
background: url('multiple-select.png') no-repeat 100% -22px, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
|
||||
background: url('multiple-select.png') no-repeat 100% -22px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
|
||||
background: url('multiple-select.png') no-repeat 100% -22px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
|
||||
background: url('multiple-select.png') no-repeat 100% -22px, -ms-linear-gradient(top, #ffffff 85%, #eeeeee 99%);
|
||||
background: url('multiple-select.png') no-repeat 100% -22px, linear-gradient(top, #ffffff 85%, #eeeeee 99%);
|
||||
}
|
||||
|
||||
.ms-search, .ms-search input {
|
||||
-webkit-box-sizing: border-box;
|
||||
-khtml-box-sizing: border-box;
|
||||
-moz-box-sizing: border-box;
|
||||
-ms-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.ms-drop ul {
|
||||
overflow: auto;
|
||||
margin: 0;
|
||||
padding: 5px 8px;
|
||||
}
|
||||
|
||||
.ms-drop ul > li {
|
||||
list-style: none;
|
||||
display: list-item;
|
||||
background-image: none;
|
||||
position: static;
|
||||
}
|
||||
|
||||
.ms-drop ul > li .disabled {
|
||||
opacity: .35;
|
||||
filter: Alpha(Opacity=35);
|
||||
}
|
||||
|
||||
.ms-drop ul > li.multiple {
|
||||
display: block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
.ms-drop ul > li.group {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.ms-drop ul > li.multiple label {
|
||||
width: 100%;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.ms-drop ul > li label {
|
||||
font-weight: normal;
|
||||
display: block;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.ms-drop ul > li label.optgroup {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.ms-drop input[type="checkbox"] {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.ms-drop .ms-no-results {
|
||||
display: none;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
|
@ -30,12 +30,6 @@ $shadow-color: rgb(223, 223, 223);
|
|||
|
||||
$background-bouton-color: hsl(0, 0%, 90%);
|
||||
|
||||
/*--------------------------MEDIA QUERY HELPERS------------------------*/
|
||||
$small-devices: 576px;
|
||||
$medium-devices: 768px;
|
||||
$large-devices: 992px;
|
||||
$extra-large-devices: 1200px;
|
||||
|
||||
/*--------------------------------GENERAL------------------------------*/
|
||||
|
||||
body {
|
||||
|
@ -1570,6 +1564,7 @@ footer {
|
|||
form {
|
||||
margin: 0px auto;
|
||||
margin-bottom: 10px;
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
label {
|
||||
|
@ -1673,512 +1668,3 @@ label {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/* --------------------------------------pedagogy-----------------------------------*/
|
||||
|
||||
$pedagogy-blue: #1bb9ea;
|
||||
$pedagogy-orange: #ea7900;
|
||||
$pedagogy-hover-blue: #0e97ce;
|
||||
$pedagogy-light-blue: #caf0ff;
|
||||
$pedagogy-white-text: #f0f0f0;
|
||||
|
||||
.pedagogy {
|
||||
&.star-not-checked {
|
||||
color : #f7f7f7;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
&.star-checked {
|
||||
color: $pedagogy-orange;
|
||||
margin-bottom: 0px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
|
||||
&.grade-without-star {
|
||||
display: none
|
||||
}
|
||||
|
||||
@media screen and (max-width: $large-devices){
|
||||
&.star-not-checked {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
&.star-checked {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: $small-devices){
|
||||
&.grade-without-star {
|
||||
display: block;
|
||||
}
|
||||
&.grade-with-star {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
#dynamic_view {
|
||||
font-size: 1.1em;
|
||||
|
||||
overflow-wrap: break-word;
|
||||
|
||||
td {
|
||||
text-align: center;
|
||||
border: none;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#search_form {
|
||||
|
||||
.search-form-container {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
grid-template-rows: auto auto auto;
|
||||
grid-template-areas:
|
||||
"action-bar action-bar"
|
||||
"search-bar search-bar"
|
||||
"radio-department radio-department"
|
||||
"radio-credit-type radio-semester";
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
grid-area: action-bar;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
grid-area: search-bar;
|
||||
display: grid;
|
||||
grid-template-columns: auto 200px;
|
||||
grid-template-rows: auto;
|
||||
grid-template-areas: "search-bar-input search-bar-button";
|
||||
|
||||
@media screen and (max-width: $medium-devices){
|
||||
grid-template-columns: auto auto;
|
||||
grid-template-rows: auto;
|
||||
grid-template-areas: "search-bar-input search-bar-button";
|
||||
}
|
||||
|
||||
@media screen and (max-width: $small-devices){
|
||||
grid-template-columns: auto;
|
||||
grid-template-rows: auto;
|
||||
grid-template-areas: "search-bar-input";
|
||||
|
||||
.search-bar-button {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.search-bar-input {
|
||||
grid-area: search-bar-input;
|
||||
background: $pedagogy-light-blue;
|
||||
}
|
||||
|
||||
.search-bar-button {
|
||||
grid-area: search-bar-button;
|
||||
background: $pedagogy-orange;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.radio-department {
|
||||
grid-area: radio-department;
|
||||
}
|
||||
|
||||
.radio-credit-type {
|
||||
grid-area: radio-credit-type;
|
||||
}
|
||||
|
||||
.radio-semester {
|
||||
grid-area: radio-semester;
|
||||
}
|
||||
|
||||
.radio-guide input[type="radio"],input[type="checkbox"] {
|
||||
display:none;
|
||||
}
|
||||
.radio-guide {
|
||||
margin-top: 10px;
|
||||
color: white;
|
||||
}
|
||||
.radio-guide label {
|
||||
display: inline-block;
|
||||
background-color: $pedagogy-blue;
|
||||
padding: 10px 20px;
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 16px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.radio-guide input[type="radio"]:checked + label {
|
||||
background-color: $pedagogy-orange;
|
||||
}
|
||||
.radio-guide input[type="checkbox"]:checked + label {
|
||||
background-color: $pedagogy-orange;
|
||||
}
|
||||
.radio-guide label:hover {
|
||||
background-color: $pedagogy-hover-blue;
|
||||
}
|
||||
}
|
||||
|
||||
#uv_detail {
|
||||
color: #062f38;
|
||||
|
||||
.uv-quick-info-container {
|
||||
display: grid;
|
||||
grid-template-columns: 20% 20% 20% 20% auto;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-areas:
|
||||
"hours-cm hours-td hours-tp hours-te hours-the"
|
||||
"department credit-type semester . ." ;
|
||||
}
|
||||
|
||||
.department {
|
||||
grid-area: department;
|
||||
}
|
||||
|
||||
.credit-type {
|
||||
grid-area: credit-type;
|
||||
}
|
||||
|
||||
.semester {
|
||||
grid-area: semester;
|
||||
}
|
||||
|
||||
.hours-cm {
|
||||
grid-area: hours-cm;
|
||||
}
|
||||
|
||||
.hours-td {
|
||||
grid-area: hours-td;
|
||||
}
|
||||
|
||||
.hours-tp {
|
||||
grid-area: hours-tp;
|
||||
}
|
||||
|
||||
.hours-te {
|
||||
grid-area: hours-te;
|
||||
}
|
||||
|
||||
.hours-the {
|
||||
grid-area: hours-the;
|
||||
}
|
||||
|
||||
#leave_comment_not_allowed {
|
||||
p {
|
||||
text-align: center;
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
|
||||
#leave_comment {
|
||||
.leave-comment-grid-container {
|
||||
display: grid;
|
||||
grid-template-columns: 270px auto;
|
||||
grid-template-rows: 100%;
|
||||
grid-template-areas: "stars comment";
|
||||
|
||||
@media screen and (max-width: $large-devices){
|
||||
grid-template-columns: 100%;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-areas:
|
||||
"stars"
|
||||
"comment";
|
||||
}
|
||||
}
|
||||
|
||||
.ui-accordion-content {
|
||||
background-color: $white-color;
|
||||
border-color: $pedagogy-orange;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.form-stars {
|
||||
grid-area: stars;
|
||||
}
|
||||
|
||||
.form-comment {
|
||||
grid-area: comment;
|
||||
}
|
||||
|
||||
.ui-accordion-header {
|
||||
background-color: $pedagogy-orange;
|
||||
color: $pedagogy-white-text;
|
||||
clip-path: polygon(0 0%, 0 100%, 30% 100%, 33% 0);
|
||||
|
||||
@media screen and (max-width: $large-devices){
|
||||
clip-path: none;
|
||||
}
|
||||
}
|
||||
|
||||
.ui-accordion-header-icon {
|
||||
color: $pedagogy-white-text;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.input-stars {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
input[type="submit"] {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.uv-details-container {
|
||||
display: grid;
|
||||
grid-template-columns: 150px 100px auto;
|
||||
grid-template-rows: 156px 1fr;
|
||||
grid-template-areas:
|
||||
"grade grade-stars uv-infos"
|
||||
". . uv-infos";
|
||||
|
||||
@media screen and (max-width: $large-devices){
|
||||
grid-template-columns: 50% 50%;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-areas:
|
||||
"grade grade-stars"
|
||||
"uv-infos uv-infos";
|
||||
}
|
||||
}
|
||||
|
||||
.grade {
|
||||
grid-area: grade;
|
||||
color: $pedagogy-white-text;
|
||||
background-color: $pedagogy-blue;
|
||||
padding-right: 10px;
|
||||
|
||||
> p {
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.grade-stars {
|
||||
grid-area: grade-stars;
|
||||
color: $pedagogy-white-text;
|
||||
background-color: $pedagogy-blue;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.uv-infos {
|
||||
grid-area: uv-infos;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.comment-container {
|
||||
display: grid;
|
||||
grid-template-columns: 300px auto;
|
||||
grid-template-rows: auto auto auto;
|
||||
grid-template-areas:
|
||||
"grade-block comment"
|
||||
"grade-block info"
|
||||
"comment-end-bar comment-end-bar";
|
||||
margin-bottom: 30px;
|
||||
margin-top: 10px;
|
||||
|
||||
@media screen and (max-width: $large-devices){
|
||||
grid-template-columns: auto;
|
||||
grid-template-rows: auto auto auto auto;
|
||||
grid-template-areas:
|
||||
"grade-block"
|
||||
"comment"
|
||||
"info"
|
||||
"comment-end-bar"
|
||||
}
|
||||
|
||||
.grade-block {
|
||||
grid-area: grade-block;
|
||||
width: 300px;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 150px 150px;
|
||||
grid-template-rows: 156px auto;
|
||||
grid-template-areas:
|
||||
"grade-type grade-stars"
|
||||
"grade-extension grade-extension";
|
||||
grid-gap: 15px;
|
||||
|
||||
clip-path: polygon(0 0, 0 100%, 100% 100%, 100% 30px, 270px 0);
|
||||
align-items: start;
|
||||
|
||||
background-color: $pedagogy-blue;
|
||||
|
||||
@media screen and (max-width: $large-devices){
|
||||
grid-template-columns: 50% auto;
|
||||
grid-template-rows: auto;
|
||||
grid-template-areas:"grade-type grade-stars";
|
||||
width: auto;
|
||||
clip-path: none;
|
||||
align-content: space-evenly;
|
||||
align-items: end;
|
||||
}
|
||||
|
||||
.grade-extension {
|
||||
grid-area: grade-extension;
|
||||
background-color: $pedagogy-blue;
|
||||
}
|
||||
|
||||
.grade-type {
|
||||
grid-area: grade-type;
|
||||
|
||||
> p {
|
||||
color: $pedagogy-white-text;
|
||||
font-weight: bold;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.grade-stars {
|
||||
grid-area: grade-stars;
|
||||
}
|
||||
}
|
||||
|
||||
.comment {
|
||||
grid-area: comment;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: auto;
|
||||
grid-template-rows: auto auto;
|
||||
grid-template-areas:
|
||||
"anchor"
|
||||
"markdown";
|
||||
|
||||
@media screen and (max-width: $large-devices){
|
||||
border-left: solid;
|
||||
border-right: solid;
|
||||
border-color: $pedagogy-blue;
|
||||
}
|
||||
|
||||
.anchor {
|
||||
grid-area: anchor;
|
||||
text-align: right;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.markdown {
|
||||
grid-area: markdown;
|
||||
|
||||
min-height: 139px;
|
||||
margin-top: 0px;
|
||||
margin-right: 0px;
|
||||
padding: 10px;
|
||||
text-align: justify;
|
||||
overflow: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.info {
|
||||
grid-area: info;
|
||||
padding-bottom: 10px;
|
||||
|
||||
@media screen and (max-width: $large-devices){
|
||||
border-left: solid;
|
||||
border-right: solid;
|
||||
border-color: $pedagogy-blue;
|
||||
}
|
||||
|
||||
.status-reported {
|
||||
color: red;
|
||||
float: left;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
.comment-end-bar {
|
||||
grid-area: comment-end-bar;
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: 33% auto auto;
|
||||
grid-template-rows: 2.5em;
|
||||
grid-template-areas: "author date report";
|
||||
|
||||
background-color: $pedagogy-blue;
|
||||
margin-top: -1px;
|
||||
|
||||
@media screen and (max-width: $large-devices){
|
||||
grid-template-columns: auto;
|
||||
grid-template-rows: auto auto auto;
|
||||
grid-template-areas:
|
||||
"report"
|
||||
"date"
|
||||
"author";
|
||||
margin-top: 0px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.author {
|
||||
grid-area: author;
|
||||
|
||||
padding-top: 6px;
|
||||
padding-left: 20px;
|
||||
|
||||
background-color: $pedagogy-orange;
|
||||
clip-path: polygon(0 10px, 0 100%, 350px 200%, 300px 10px);
|
||||
|
||||
@media screen and (max-width: $large-devices){
|
||||
clip-path: none;
|
||||
padding: 0px;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $pedagogy-white-text;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: $pedagogy-hover-blue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.date {
|
||||
grid-area: date;
|
||||
color: $pedagogy-white-text;
|
||||
|
||||
@media screen and (max-width: $large-devices){
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
}
|
||||
|
||||
.report {
|
||||
grid-area: report;
|
||||
justify-self: right;
|
||||
padding-right: 30px;
|
||||
padding-left: 30px;
|
||||
|
||||
a {
|
||||
color: $pedagogy-white-text;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: $pedagogy-hover-blue;
|
||||
}
|
||||
|
||||
@media screen and (max-width: $large-devices){
|
||||
text-align: center;
|
||||
justify-self: inherit;
|
||||
padding-bottom: 7px;
|
||||
background-color: $white-color;
|
||||
|
||||
border-left: solid;
|
||||
border-right: solid;
|
||||
border-color: $pedagogy-blue;
|
||||
|
||||
a {
|
||||
color: $black-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,6 +5,7 @@
|
|||
<title>{% block title %}{% trans %}Welcome!{% endtrans %}{% endblock %} - Association des Étudiants UTBM</title>
|
||||
<link rel="shortcut icon" href="{{ static('core/img/favicon.ico') }}">
|
||||
<link rel="stylesheet" href="{{ static('core/base.css') }}">
|
||||
<link rel="stylesheet" href="{{ static('core/multiple-select.css') }}">
|
||||
<link rel="stylesheet" href="{{ static('core/jquery.datetimepicker.min.css') }}">
|
||||
<link rel="stylesheet" href="{{ static('ajax_select/css/ajax_select.css') }}">
|
||||
<link rel="stylesheet" href="{{ scss('core/style.scss') }}">
|
||||
|
@ -38,7 +39,7 @@
|
|||
</div>
|
||||
|
||||
<header>
|
||||
{% if not user.is_authenticated %}
|
||||
{% if not user.is_authenticated() %}
|
||||
<div id="header_logo" style="background-image: url('{{ static('core/img/logo.png') }}'); width: 185px; height: 100px;">
|
||||
<a href="{{ url('core:index') }}"></a>
|
||||
</div>
|
||||
|
@ -59,23 +60,21 @@
|
|||
</div>
|
||||
<div id="header_bar">
|
||||
<ul id="header_bars_infos">
|
||||
{% cache 100 counters_activity %}
|
||||
{% for bar in Counter.objects.filter(type="BAR").all() %}
|
||||
<li>
|
||||
<a href="{{ url('counter:activity', counter_id=bar.id) }}" style="padding: 0px">
|
||||
{% if bar.is_inactive(): %}
|
||||
<i class="fa fa-question" style="color: #f39c12"></i>
|
||||
{% elif bar.is_open(): %}
|
||||
<i class="fa fa-check" style="color: #2ecc71"></i>
|
||||
{% else %}
|
||||
<i class="fa fa-times" style="color: #eb2f06"></i>
|
||||
{% endif %}
|
||||
{{ bar }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% endcache %}
|
||||
{% for bar in Counter.objects.filter(type="BAR").all() %}
|
||||
<li>
|
||||
<a href="{{ url('counter:activity', counter_id=bar.id) }}" style="padding: 0px">
|
||||
{% if bar.is_inactive(): %}
|
||||
<i class="fa fa-question" style="color: #f39c12"></i>
|
||||
{% elif bar.is_open(): %}
|
||||
<i class="fa fa-check" style="color: #2ecc71"></i>
|
||||
{% else %}
|
||||
<i class="fa fa-times" style="color: #eb2f06"></i>
|
||||
{% endif %}
|
||||
{{ bar }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
<form action="{{ url('core:search') }}" method="GET" id="header_search">
|
||||
<input type="text" placeholder="{% trans %}Search{% endtrans %}" name="query" id="search" />
|
||||
<input type="submit" value="{% trans %}Search{% endtrans %}" style="display: none;" />
|
||||
|
@ -153,7 +152,7 @@
|
|||
<nav>
|
||||
<a href="{{ url('core:index') }}">{% trans %}Main{% endtrans %}</a>
|
||||
<div class="dropdown">
|
||||
<button class="dropbtn">{% trans %}Associations & Clubs{% endtrans %}
|
||||
<button class="dropbtn">{% trans %}Associations & Clubs{% endtrans %}
|
||||
<i class="fa fa-caret-down"></i>
|
||||
</button>
|
||||
<div class="dropdown-content">
|
||||
|
@ -165,30 +164,30 @@
|
|||
<a href="{{ url('core:page', page_name='clubs/doceo') }}">{% trans %}Doceo{% endtrans %}</a>
|
||||
<a href="{{ url('core:page', page_name='positions') }}">{% trans %}Positions{% endtrans %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button class="dropbtn">{% trans %}Events{% endtrans %}
|
||||
<button class="dropbtn">{% trans %}Events{% endtrans %}
|
||||
<i class="fa fa-caret-down"></i>
|
||||
</button>
|
||||
<div class="dropdown-content">
|
||||
<a href="{{ url('core:page', page_name='Index/calendrier_evenements') }}">{% trans %}Calendar{% endtrans %}</a>
|
||||
<a href="{{ url('core:page', page_name='ga') }}">{% trans %}Big event{% endtrans %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a href="{{ url('forum:main') }}">{% trans %}Forum{% endtrans %}</a>
|
||||
<a href="{{ url('sas:main') }}">{% trans %}Gallery{% endtrans %}</a>
|
||||
<a href="{{ url('eboutic:main') }}">{% trans %}Eboutic{% endtrans %}</a>
|
||||
<div class="dropdown">
|
||||
<button class="dropbtn">{% trans %}Services{% endtrans %}
|
||||
<button class="dropbtn">{% trans %}Services{% endtrans %}
|
||||
<i class="fa fa-caret-down"></i>
|
||||
</button>
|
||||
<div class="dropdown-content">
|
||||
<a href="{{ url('matmat:search_clear') }}">{% trans %}Matmatronch{% endtrans %}</a>
|
||||
<a href="/launderette">{% trans %}Launderette{% endtrans %}</a>
|
||||
<a href="{{ url('core:file_list') }}">{% trans %}Files{% endtrans %}</a>
|
||||
<a href="{{ url('pedagogy:guide') }}">{% trans %}Pedagogy{% endtrans %}</a>
|
||||
{# <a href="https://ae2.utbm.fr/uvs/">{% trans %}Pedagogy{% endtrans %}</a> #}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button class="dropbtn">{% trans %}My Benefits{% endtrans %}
|
||||
<i class="fa fa-caret-down"></i>
|
||||
|
@ -199,7 +198,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="dropdown">
|
||||
<button class="dropbtn">{% trans %}Help{% endtrans %}
|
||||
<button class="dropbtn">{% trans %}Help{% endtrans %}
|
||||
<i class="fa fa-caret-down"></i>
|
||||
</button>
|
||||
<div class="dropdown-content">
|
||||
|
@ -207,7 +206,7 @@
|
|||
<a href="{{ url('core:page', 'contacts') }}">{% trans %}Contacts{% endtrans %}</a>
|
||||
<a href="{{ url('core:page', page_name="Index") }}">{% trans %}Wiki{% endtrans %}</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
@ -265,9 +264,22 @@
|
|||
<script src="{{ static('core/js/ui/jquery-ui.min.js') }}"></script>
|
||||
<script src="{{ static('core/js/ui/i18n/datepicker-fr.js') }}"></script>
|
||||
<script src="{{ static('core/js/jquery.datetimepicker.full.min.js') }}"></script>
|
||||
<script src="{{ static('core/js/multiple-select.js') }}"></script>
|
||||
<script src="{{ static('ajax_select/js/ajax_select.js') }}"></script>
|
||||
<script src="{{ url('javascript-catalog') }}"></script>
|
||||
<script>
|
||||
$('.select_single').multipleSelect({
|
||||
single: true,
|
||||
{% if not popup %}
|
||||
position: 'top',
|
||||
{% endif %}
|
||||
});
|
||||
$('.select_multiple').multipleSelect({
|
||||
filter: true,
|
||||
{% if not popup %}
|
||||
position: 'top',
|
||||
{% endif %}
|
||||
});
|
||||
$('.select_date').datepicker({
|
||||
changeMonth: true,
|
||||
changeYear: true,
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
{% extends "core/base.jinja" %}
|
||||
|
||||
{% block title %}
|
||||
{% trans %}Welcome!{% endtrans %}
|
||||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
{{ get_sith().index_page|markdown }}
|
||||
{% endblock %}
|
|
@ -11,11 +11,11 @@
|
|||
{% endif %}
|
||||
|
||||
{% if next %}
|
||||
{% if user.is_authenticated %}
|
||||
{% if user.is_authenticated() %}
|
||||
<p>{% trans %}Your account doesn't have access to this page. To proceed,
|
||||
please login with an account that has access.{% endtrans %}</p>
|
||||
{% else %}
|
||||
<p>{% trans %}Please login or create an account to see this page.{% endtrans %}</p>
|
||||
<p>{% trans %}Please login to see this page.{% endtrans %}</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
|
@ -30,6 +30,5 @@
|
|||
|
||||
{# Assumes you setup the password_reset view in your URLconf #}
|
||||
<p><a href="{{ url('core:password_reset') }}">{% trans %}Lost password?{% endtrans %}</a></p>
|
||||
<p><a href="{{ url('core:register') }}">{% trans %}Create account{% endtrans %}</a></p>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
@ -114,24 +114,22 @@
|
|||
{% endmacro %}
|
||||
|
||||
{% macro paginate(page_obj, paginator) %}
|
||||
{% if page_obj.has_previous() or page_obj.has_next() %}
|
||||
{% if page_obj.has_previous() %}
|
||||
<a href="?page={{ page_obj.previous_page_number() }}">{% trans %}Previous{% endtrans %}</a>
|
||||
{% else %}
|
||||
<span class="disabled">{% trans %}Previous{% endtrans %}</span>
|
||||
{% endif %}
|
||||
{% for i in paginator.page_range %}
|
||||
{% if page_obj.number == i %}
|
||||
<span class="active">{{ i }} <span class="sr-only">({% trans %}current{% endtrans %})</span></span>
|
||||
{% else %}
|
||||
<a href="?page={{ i }}">{{ i }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if page_obj.has_next() %}
|
||||
<a href="?page={{ page_obj.next_page_number() }}">{% trans %}Next{% endtrans %}</a>
|
||||
{% for i in paginator.page_range %}
|
||||
{% if page_obj.number == i %}
|
||||
<span class="active">{{ i }} <span class="sr-only">({% trans %}current{% endtrans %})</span></span>
|
||||
{% else %}
|
||||
<span class="disabled">{% trans %}Next{% endtrans %}</span>
|
||||
<a href="?page={{ i }}">{{ i }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if page_obj.has_next() %}
|
||||
<a href="?page={{ page_obj.next_page_number() }}">{% trans %}Next{% endtrans %}</a>
|
||||
{% else %}
|
||||
<span class="disabled">{% trans %}Next{% endtrans %}</span>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
|
|
|
@ -5,199 +5,168 @@
|
|||
{# The easymde script can be included twice, it's safe in the code #}
|
||||
<script src="{{ statics.js }}"> </script>
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
const css = "{{ statics.css }}";
|
||||
let lastAPICall;
|
||||
var css = "{{ statics.css }}";
|
||||
var lastAPICall;
|
||||
|
||||
// Only import the css once
|
||||
if (!document.head.innerHTML.includes(css)) {
|
||||
document.head.innerHTML += '<link rel="stylesheet" href="' + css + '">';
|
||||
}
|
||||
// Only import the css once
|
||||
if (!document.head.innerHTML.includes(css)){
|
||||
document.head.innerHTML += '<link rel="stylesheet" href="' + css + '">';
|
||||
}
|
||||
|
||||
// Custom markdown parser
|
||||
function customMarkdownParser(plainText, cb) {
|
||||
$.ajax({
|
||||
url: "{{ markdown_api_url }}",
|
||||
method: "POST",
|
||||
data: { text: plainText, csrfmiddlewaretoken: getCSRFToken() },
|
||||
}).done(cb);
|
||||
}
|
||||
|
||||
// Pretty markdown input
|
||||
const easymde = new EasyMDE({
|
||||
element: document.getElementById("{{ widget.attrs.id }}"),
|
||||
spellChecker: false,
|
||||
autoDownloadFontAwesome: false,
|
||||
previewRender: function(plainText, preview) { // Async method
|
||||
clearTimeout(lastAPICall);
|
||||
lastAPICall = setTimeout(() => {
|
||||
customMarkdownParser(plainText, (msg) => preview.innerHTML = msg);
|
||||
}, 300);
|
||||
return preview.innerHTML;
|
||||
},
|
||||
forceSync: true, // Avoid validation error on generic create view
|
||||
toolbar: [
|
||||
{
|
||||
name: "heading-smaller",
|
||||
action: EasyMDE.toggleHeadingSmaller,
|
||||
className: "fa fa-header",
|
||||
title: "{{ translations.heading_smaller }}"
|
||||
},
|
||||
{
|
||||
name: "italic",
|
||||
action: EasyMDE.toggleItalic,
|
||||
className: "fa fa-italic",
|
||||
title: "{{ translations.italic }}"
|
||||
},
|
||||
{
|
||||
name: "bold",
|
||||
action: EasyMDE.toggleBold,
|
||||
className: "fa fa-bold",
|
||||
title: "{{ translations.bold }}"
|
||||
},
|
||||
{
|
||||
name: "strikethrough",
|
||||
action: EasyMDE.toggleStrikethrough,
|
||||
className: "fa fa-strikethrough",
|
||||
title: "{{ translations.strikethrough }}"
|
||||
},
|
||||
{
|
||||
name: "underline",
|
||||
action: function customFunction(editor){
|
||||
let cm = editor.codemirror;
|
||||
cm.replaceSelection('__' + cm.getSelection() + '__');
|
||||
},
|
||||
className: "fa fa-underline",
|
||||
title: "{{ translations.underline }}"
|
||||
},
|
||||
{
|
||||
name: "superscript",
|
||||
action: function customFunction(editor){
|
||||
let cm = editor.codemirror;
|
||||
cm.replaceSelection('<sup>' + cm.getSelection() + '</sup>');
|
||||
},
|
||||
className: "fa fa-superscript",
|
||||
title: "{{ translations.superscript }}"
|
||||
},
|
||||
{
|
||||
name: "subscript",
|
||||
action: function customFunction(editor){
|
||||
let cm = editor.codemirror;
|
||||
cm.replaceSelection('<sub>' + cm.getSelection() + '</sub>');
|
||||
},
|
||||
className: "fa fa-subscript",
|
||||
title: "{{ translations.subscript }}"
|
||||
},
|
||||
{
|
||||
name: "code",
|
||||
action: EasyMDE.toggleCodeBlock,
|
||||
className: "fa fa-code",
|
||||
title: "{{ translations.code }}"
|
||||
},
|
||||
"|",
|
||||
{
|
||||
name: "quote",
|
||||
action: EasyMDE.toggleBlockquote,
|
||||
className: "fa fa-quote-left",
|
||||
title: "{{ translations.quote }}"
|
||||
},
|
||||
{
|
||||
name: "unordered-list",
|
||||
action: EasyMDE.toggleUnorderedList,
|
||||
className: "fa fa-list-ul",
|
||||
title: "{{ translations.unordered_list }}"
|
||||
},
|
||||
{
|
||||
name: "ordered-list",
|
||||
action: EasyMDE.toggleOrderedList,
|
||||
className: "fa fa-list-ol",
|
||||
title: "{{ translations.ordered_list }}"
|
||||
},
|
||||
"|",
|
||||
{
|
||||
name: "link",
|
||||
action: EasyMDE.drawLink,
|
||||
className: "fa fa-link",
|
||||
title: "{{ translations.link }}"
|
||||
},
|
||||
{
|
||||
name: "image",
|
||||
action: EasyMDE.drawImage,
|
||||
className: "fa fa-picture-o",
|
||||
title: "{{ translations.image }}"
|
||||
},
|
||||
{
|
||||
name: "table",
|
||||
action: EasyMDE.drawTable,
|
||||
className: "fa fa-table",
|
||||
title: "{{ translations.table }}"
|
||||
},
|
||||
"|",
|
||||
{
|
||||
name: "clean-block",
|
||||
action: EasyMDE.cleanBlock,
|
||||
className: "fa fa-eraser fa-clean-block",
|
||||
title: "{{ translations.clean_block }}"
|
||||
},
|
||||
"|",
|
||||
{
|
||||
name: "preview",
|
||||
action: EasyMDE.togglePreview,
|
||||
className: "fa fa-eye no-disable",
|
||||
title: "{{ translations.preview }}"
|
||||
},
|
||||
{
|
||||
name: "side-by-side",
|
||||
action: EasyMDE.toggleSideBySide,
|
||||
className: "fa fa-columns no-disable no-mobile",
|
||||
title: "{{ translations.side_by_side }}"
|
||||
},
|
||||
{
|
||||
name: "fullscreen",
|
||||
action: EasyMDE.toggleFullScreen,
|
||||
className: "fa fa-arrows-alt no-disable no-mobile",
|
||||
title: "{{ translations.fullscreen }}"
|
||||
},
|
||||
"|",
|
||||
{
|
||||
name: "guide",
|
||||
action: "/page/Aide_sur_la_syntaxe",
|
||||
className: "fa fa-question-circle",
|
||||
title: "{{ translations.guide }}"
|
||||
},
|
||||
]
|
||||
// Custom markdown parser
|
||||
function customMarkdownParser(plainText, preview) {
|
||||
$.ajax({
|
||||
url: "{{ markdown_api_url }}",
|
||||
method: "POST",
|
||||
data: { text: plainText, csrfmiddlewaretoken: getCSRFToken() },
|
||||
}).done(function (msg) {
|
||||
preview.innerHTML = msg;
|
||||
});
|
||||
}
|
||||
|
||||
const textarea = document.getElementById('{{ widget.attrs.id }}');
|
||||
const submits = textarea
|
||||
.closest('form')
|
||||
.querySelectorAll('input[type="submit"]');
|
||||
const parentDiv = textarea.parentElement;
|
||||
let submitPressed = false;
|
||||
|
||||
function checkMarkdownInput(e) {
|
||||
// an attribute is null if it does not exist, else a string
|
||||
const required = textarea.getAttribute('required') != null;
|
||||
const length = textarea.value.trim().length;
|
||||
|
||||
if (required && length == 0) {
|
||||
parentDiv.style.boxShadow = 'red 0px 0px 1.5px 1px';
|
||||
} else {
|
||||
parentDiv.style.boxShadow = '';
|
||||
}
|
||||
}
|
||||
|
||||
function onSubmitClick(e) {
|
||||
if (!submitPressed) {
|
||||
easymde.codemirror.on('change', checkMarkdownInput);
|
||||
}
|
||||
submitPressed = true;
|
||||
checkMarkdownInput(e);
|
||||
}
|
||||
|
||||
submits.forEach((submit) => {
|
||||
submit.addEventListener('click', onSubmitClick);
|
||||
})
|
||||
})
|
||||
// Pretty markdown input
|
||||
var easymde = new EasyMDE({
|
||||
element: document.getElementById("{{ widget.attrs.id }}"),
|
||||
spellChecker: false,
|
||||
autoDownloadFontAwesome: false,
|
||||
previewRender: function(plainText, preview){ // Async method
|
||||
clearTimeout(lastAPICall);
|
||||
lastAPICall = setTimeout(function (plainText, preview){
|
||||
customMarkdownParser(plainText, preview);
|
||||
}, 300, plainText, preview);
|
||||
return preview.innerHTML;
|
||||
},
|
||||
forceSync: true, // Avoid validation error on generic create view
|
||||
toolbar: [
|
||||
{
|
||||
name: "heading-smaller",
|
||||
action: EasyMDE.toggleHeadingSmaller,
|
||||
className: "fa fa-header",
|
||||
title: "{{ translations.heading_smaller }}"
|
||||
},
|
||||
{
|
||||
name: "italic",
|
||||
action: EasyMDE.toggleItalic,
|
||||
className: "fa fa-italic",
|
||||
title: "{{ translations.italic }}"
|
||||
},
|
||||
{
|
||||
name: "bold",
|
||||
action: EasyMDE.toggleBold,
|
||||
className: "fa fa-bold",
|
||||
title: "{{ translations.bold }}"
|
||||
},
|
||||
{
|
||||
name: "strikethrough",
|
||||
action: EasyMDE.toggleStrikethrough,
|
||||
className: "fa fa-strikethrough",
|
||||
title: "{{ translations.strikethrough }}"
|
||||
},
|
||||
{
|
||||
name: "underline",
|
||||
action: function customFunction(editor){
|
||||
var cm = editor.codemirror;
|
||||
cm.replaceSelection('__' + cm.getSelection() + '__');
|
||||
},
|
||||
className: "fa fa-underline",
|
||||
title: "{{ translations.underline }}"
|
||||
},
|
||||
{
|
||||
name: "superscript",
|
||||
action: function customFunction(editor){
|
||||
var cm = editor.codemirror;
|
||||
cm.replaceSelection('<sup>' + cm.getSelection() + '</sup>');
|
||||
},
|
||||
className: "fa fa-superscript",
|
||||
title: "{{ translations.superscript }}"
|
||||
},
|
||||
{
|
||||
name: "subscript",
|
||||
action: function customFunction(editor){
|
||||
var cm = editor.codemirror;
|
||||
cm.replaceSelection('<sub>' + cm.getSelection() + '</sub>');
|
||||
},
|
||||
className: "fa fa-subscript",
|
||||
title: "{{ translations.subscript }}"
|
||||
},
|
||||
{
|
||||
name: "code",
|
||||
action: EasyMDE.toggleCodeBlock,
|
||||
className: "fa fa-code",
|
||||
title: "{{ translations.code }}"
|
||||
},
|
||||
"|",
|
||||
{
|
||||
name: "quote",
|
||||
action: EasyMDE.toggleBlockquote,
|
||||
className: "fa fa-quote-left",
|
||||
title: "{{ translations.quote }}"
|
||||
},
|
||||
{
|
||||
name: "unordered-list",
|
||||
action: EasyMDE.toggleUnorderedList,
|
||||
className: "fa fa-list-ul",
|
||||
title: "{{ translations.unordered_list }}"
|
||||
},
|
||||
{
|
||||
name: "ordered-list",
|
||||
action: EasyMDE.toggleOrderedList,
|
||||
className: "fa fa-list-ol",
|
||||
title: "{{ translations.ordered_list }}"
|
||||
},
|
||||
"|",
|
||||
{
|
||||
name: "link",
|
||||
action: EasyMDE.drawLink,
|
||||
className: "fa fa-link",
|
||||
title: "{{ translations.link }}"
|
||||
},
|
||||
{
|
||||
name: "image",
|
||||
action: EasyMDE.drawImage,
|
||||
className: "fa fa-picture-o",
|
||||
title: "{{ translations.image }}"
|
||||
},
|
||||
{
|
||||
name: "table",
|
||||
action: EasyMDE.drawTable,
|
||||
className: "fa fa-table",
|
||||
title: "{{ translations.table }}"
|
||||
},
|
||||
"|",
|
||||
{
|
||||
name: "clean-block",
|
||||
action: EasyMDE.cleanBlock,
|
||||
className: "fa fa-eraser fa-clean-block",
|
||||
title: "{{ translations.clean_block }}"
|
||||
},
|
||||
"|",
|
||||
{
|
||||
name: "preview",
|
||||
action: EasyMDE.togglePreview,
|
||||
className: "fa fa-eye no-disable",
|
||||
title: "{{ translations.preview }}"
|
||||
},
|
||||
{
|
||||
name: "side-by-side",
|
||||
action: EasyMDE.toggleSideBySide,
|
||||
className: "fa fa-columns no-disable no-mobile",
|
||||
title: "{{ translations.side_by_side }}"
|
||||
},
|
||||
{
|
||||
name: "fullscreen",
|
||||
action: EasyMDE.toggleFullScreen,
|
||||
className: "fa fa-arrows-alt no-disable no-mobile",
|
||||
title: "{{ translations.fullscreen }}"
|
||||
},
|
||||
"|",
|
||||
{
|
||||
name: "guide",
|
||||
action: "/page/Aide_sur_la_syntaxe",
|
||||
className: "fa fa-question-circle",
|
||||
title: "{{ translations.guide }}"
|
||||
},
|
||||
]
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
|
@ -1,14 +1,10 @@
|
|||
{% extends "core/base.jinja" %}
|
||||
|
||||
{% block content %}
|
||||
{% if form %}
|
||||
<form method="post" action="">
|
||||
{% csrf_token %}
|
||||
{{ form.as_p() }}
|
||||
<input type="submit" value="{% trans %}Reset{% endtrans %}" />
|
||||
</form>
|
||||
{% else %}
|
||||
{% trans %}It seems that this link has expired. To generate a new link, you can follow this link: {% endtrans %}<a href="{{ url('core:password_change') }}">{% trans %}lost password{% endtrans %}</a>.
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
|
|
@ -200,7 +200,7 @@ $( function() {
|
|||
keys.push(e.keyCode);
|
||||
if (keys.toString()==pattern) {
|
||||
keys = [];
|
||||
$("#user_profile_pictures_bigone img").attr("src", "{{ static('core/img/yug.jpg') }}");
|
||||
$("#right_column img").attr("src", "{{ static('core/img/yug.jpg') }}");
|
||||
}
|
||||
if (keys.length==6) {
|
||||
keys.shift();
|
||||
|
|
|
@ -83,6 +83,7 @@
|
|||
<li><a href="{{ url('com:weekmail_destinations') }}">{% trans %}Weekmail destinations{% endtrans %}</a></li>
|
||||
<li><a href="{{ url('com:news_new') }}">{% trans %}Create news{% endtrans %}</a></li>
|
||||
<li><a href="{{ url('com:news_admin_list') }}">{% trans %}Moderate news{% endtrans %}</a></li>
|
||||
<li><a href="{{ url('com:index_edit') }}">{% trans %}Edit index page{% endtrans %}</a></li>
|
||||
<li><a href="{{ url('com:alert_edit') }}">{% trans %}Edit alert message{% endtrans %}</a></li>
|
||||
<li><a href="{{ url('com:info_edit') }}">{% trans %}Edit information message{% endtrans %}</a></li>
|
||||
<li><a href="{{ url('core:file_moderation') }}">{% trans %}Moderate files{% endtrans %}</a></li>
|
||||
|
@ -103,16 +104,6 @@
|
|||
<li><a href="{{ url('club:tools', club_id=m.club.id) }}">{{ m.club }}</a></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
<h4>{% trans %}Pedagogy{% endtrans %}</h4>
|
||||
<ul>
|
||||
{% if user.is_in_group(settings.SITH_GROUP_PEDAGOGY_ADMIN_ID) or user.is_root %}
|
||||
<li><a href="{{ url('pedagogy:uv_create') }}">{% trans %}Create UV{% endtrans %}</a></li>
|
||||
<li><a href="{{ url('pedagogy:moderation') }}">{% trans %}Moderate comments{% endtrans %}</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
<h4>{% trans %}Elections{% endtrans %}</h4>
|
||||
<ul>
|
||||
|
@ -122,8 +113,6 @@
|
|||
<li><a href="{{ url('election:create') }}">{% trans %}Create a new election{% endtrans %}</a></li>
|
||||
{%- endif -%}
|
||||
</ul>
|
||||
|
||||
<hr>
|
||||
<h4>{% trans %}Other tools{% endtrans %}</h4>
|
||||
<ul>
|
||||
<li><a href="{{ url('core:to_markdown') }}">{% trans %}Convert dokuwiki/BBcode syntax to Markdown{% endtrans %}</a></li>
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
import os
|
||||
|
||||
from django.test import Client, TestCase
|
||||
from django.urls import reverse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.management import call_command
|
||||
|
||||
from core.models import User, Group, Page
|
||||
|
@ -396,40 +396,6 @@ http://git.an
|
|||
)
|
||||
|
||||
|
||||
class UserToolsTest(TestCase):
|
||||
def setUp(self):
|
||||
call_command("populate")
|
||||
|
||||
def test_anonymous_user_unauthorized(self):
|
||||
response = self.client.get(reverse("core:user_tools"))
|
||||
self.assertEquals(response.status_code, 403)
|
||||
|
||||
def test_page_is_working(self):
|
||||
# Test for simple user
|
||||
self.client.login(username="guy", password="plop")
|
||||
response = self.client.get(reverse("core:user_tools"))
|
||||
self.assertNotEquals(response.status_code, 500)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
# Test for root
|
||||
self.client.login(username="root", password="plop")
|
||||
response = self.client.get(reverse("core:user_tools"))
|
||||
self.assertNotEquals(response.status_code, 500)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
# Test for skia
|
||||
self.client.login(username="skia", password="plop")
|
||||
response = self.client.get(reverse("core:user_tools"))
|
||||
self.assertNotEquals(response.status_code, 500)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
# Test for comunity
|
||||
self.client.login(username="comunity", password="plop")
|
||||
response = self.client.get(reverse("core:user_tools"))
|
||||
self.assertNotEquals(response.status_code, 500)
|
||||
self.assertEquals(response.status_code, 200)
|
||||
|
||||
|
||||
# TODO: many tests on the pages:
|
||||
# - renaming a page
|
||||
# - changing a page's parent --> check that page's children's full_name
|
||||
|
|
140
core/urls.py
140
core/urls.py
|
@ -23,209 +23,189 @@
|
|||
#
|
||||
#
|
||||
|
||||
from django.urls import re_path
|
||||
from django.conf.urls import url
|
||||
|
||||
from core.views import *
|
||||
|
||||
urlpatterns = [
|
||||
re_path(r"^$", index, name="index"),
|
||||
re_path(r"^to_markdown$", ToMarkdownView.as_view(), name="to_markdown"),
|
||||
re_path(r"^notifications$", NotificationList.as_view(), name="notification_list"),
|
||||
re_path(r"^notification/(?P<notif_id>[0-9]+)$", notification, name="notification"),
|
||||
url(r"^$", index, name="index"),
|
||||
url(r"^to_markdown$", ToMarkdownView.as_view(), name="to_markdown"),
|
||||
url(r"^notifications$", NotificationList.as_view(), name="notification_list"),
|
||||
url(r"^notification/(?P<notif_id>[0-9]+)$", notification, name="notification"),
|
||||
# Search
|
||||
re_path(r"^search/$", search_view, name="search"),
|
||||
re_path(r"^search_json/$", search_json, name="search_json"),
|
||||
re_path(r"^search_user/$", search_user_json, name="search_user"),
|
||||
url(r"^search/$", search_view, name="search"),
|
||||
url(r"^search_json/$", search_json, name="search_json"),
|
||||
url(r"^search_user/$", search_user_json, name="search_user"),
|
||||
# Login and co
|
||||
re_path(r"^login/$", SithLoginView.as_view(), name="login"),
|
||||
re_path(r"^logout/$", logout, name="logout"),
|
||||
re_path(
|
||||
r"^password_change/$", SithPasswordChangeView.as_view(), name="password_change"
|
||||
),
|
||||
re_path(
|
||||
url(r"^login/$", login, name="login"),
|
||||
url(r"^logout/$", logout, name="logout"),
|
||||
url(r"^password_change/$", password_change, name="password_change"),
|
||||
url(
|
||||
r"^password_change/(?P<user_id>[0-9]+)$",
|
||||
password_root_change,
|
||||
name="password_root_change",
|
||||
),
|
||||
re_path(
|
||||
r"^password_change/done$",
|
||||
SithPasswordChangeDoneView.as_view(),
|
||||
name="password_change_done",
|
||||
),
|
||||
re_path(
|
||||
r"^password_reset/$", SithPasswordResetView.as_view(), name="password_reset"
|
||||
),
|
||||
re_path(
|
||||
r"^password_reset/done$",
|
||||
SithPasswordResetDoneView.as_view(),
|
||||
name="password_reset_done",
|
||||
),
|
||||
re_path(
|
||||
url(r"^password_change/done$", password_change_done, name="password_change_done"),
|
||||
url(r"^password_reset/$", password_reset, name="password_reset"),
|
||||
url(r"^password_reset/done$", password_reset_done, name="password_reset_done"),
|
||||
url(
|
||||
r"^reset/(?P<uidb64>[0-9A-Za-z_\-]+)/(?P<token>[0-9A-Za-z]{1,13}-[0-9A-Za-z]{1,20})/$",
|
||||
SithPasswordResetConfirmView.as_view(),
|
||||
password_reset_confirm,
|
||||
name="password_reset_confirm",
|
||||
),
|
||||
re_path(
|
||||
r"^reset/done/$",
|
||||
SithPasswordResetCompleteView.as_view(),
|
||||
name="password_reset_complete",
|
||||
),
|
||||
re_path(r"^register$", register, name="register"),
|
||||
url(r"^reset/done/$", password_reset_complete, name="password_reset_complete"),
|
||||
url(r"^register$", register, name="register"),
|
||||
# Group handling
|
||||
re_path(r"^group/$", GroupListView.as_view(), name="group_list"),
|
||||
re_path(r"^group/new$", GroupCreateView.as_view(), name="group_new"),
|
||||
re_path(
|
||||
r"^group/(?P<group_id>[0-9]+)/$", GroupEditView.as_view(), name="group_edit"
|
||||
),
|
||||
re_path(
|
||||
url(r"^group/$", GroupListView.as_view(), name="group_list"),
|
||||
url(r"^group/new$", GroupCreateView.as_view(), name="group_new"),
|
||||
url(r"^group/(?P<group_id>[0-9]+)/$", GroupEditView.as_view(), name="group_edit"),
|
||||
url(
|
||||
r"^group/(?P<group_id>[0-9]+)/delete$",
|
||||
GroupDeleteView.as_view(),
|
||||
name="group_delete",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^group/(?P<group_id>[0-9]+)/detail$",
|
||||
GroupTemplateView.as_view(),
|
||||
name="group_detail",
|
||||
),
|
||||
# User views
|
||||
re_path(r"^user/$", UserListView.as_view(), name="user_list"),
|
||||
re_path(
|
||||
url(r"^user/$", UserListView.as_view(), name="user_list"),
|
||||
url(
|
||||
r"^user/(?P<user_id>[0-9]+)/mini$",
|
||||
UserMiniView.as_view(),
|
||||
name="user_profile_mini",
|
||||
),
|
||||
re_path(r"^user/(?P<user_id>[0-9]+)/$", UserView.as_view(), name="user_profile"),
|
||||
re_path(
|
||||
url(r"^user/(?P<user_id>[0-9]+)/$", UserView.as_view(), name="user_profile"),
|
||||
url(
|
||||
r"^user/(?P<user_id>[0-9]+)/pictures$",
|
||||
UserPicturesView.as_view(),
|
||||
name="user_pictures",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^user/(?P<user_id>[0-9]+)/godfathers$",
|
||||
UserGodfathersView.as_view(),
|
||||
name="user_godfathers",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^user/(?P<user_id>[0-9]+)/godfathers/tree$",
|
||||
UserGodfathersTreeView.as_view(),
|
||||
name="user_godfathers_tree",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^user/(?P<user_id>[0-9]+)/godfathers/tree/pict$",
|
||||
UserGodfathersTreePictureView.as_view(),
|
||||
name="user_godfathers_tree_pict",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^user/(?P<user_id>[0-9]+)/godfathers/(?P<godfather_id>[0-9]+)/(?P<is_father>(True)|(False))/delete$",
|
||||
DeleteUserGodfathers,
|
||||
name="user_godfathers_delete",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^user/(?P<user_id>[0-9]+)/edit$",
|
||||
UserUpdateProfileView.as_view(),
|
||||
name="user_edit",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^user/(?P<user_id>[0-9]+)/profile_upload$",
|
||||
UserUploadProfilePictView.as_view(),
|
||||
name="user_profile_upload",
|
||||
),
|
||||
re_path(
|
||||
r"^user/(?P<user_id>[0-9]+)/clubs$", UserClubView.as_view(), name="user_clubs"
|
||||
),
|
||||
re_path(
|
||||
url(r"^user/(?P<user_id>[0-9]+)/clubs$", UserClubView.as_view(), name="user_clubs"),
|
||||
url(
|
||||
r"^user/(?P<user_id>[0-9]+)/prefs$",
|
||||
UserPreferencesView.as_view(),
|
||||
name="user_prefs",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^user/(?P<user_id>[0-9]+)/groups$",
|
||||
UserUpdateGroupView.as_view(),
|
||||
name="user_groups",
|
||||
),
|
||||
re_path(r"^user/tools/$", UserToolsView.as_view(), name="user_tools"),
|
||||
re_path(
|
||||
url(r"^user/tools/$", UserToolsView.as_view(), name="user_tools"),
|
||||
url(
|
||||
r"^user/(?P<user_id>[0-9]+)/account$",
|
||||
UserAccountView.as_view(),
|
||||
name="user_account",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^user/(?P<user_id>[0-9]+)/account/(?P<year>[0-9]+)/(?P<month>[0-9]+)$",
|
||||
UserAccountDetailView.as_view(),
|
||||
name="user_account_detail",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^user/(?P<user_id>[0-9]+)/stats$", UserStatsView.as_view(), name="user_stats"
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^user/(?P<user_id>[0-9]+)/gift/create$",
|
||||
GiftCreateView.as_view(),
|
||||
name="user_gift_create",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^user/(?P<user_id>[0-9]+)/gift/delete/(?P<gift_id>[0-9]+)/$",
|
||||
GiftDeleteView.as_view(),
|
||||
name="user_gift_delete",
|
||||
),
|
||||
# File views
|
||||
# re_path(r'^file/add/(?P<popup>popup)?$', FileCreateView.as_view(), name='file_new'),
|
||||
re_path(r"^file/(?P<popup>popup)?$", FileListView.as_view(), name="file_list"),
|
||||
re_path(
|
||||
# url(r'^file/add/(?P<popup>popup)?$', FileCreateView.as_view(), name='file_new'),
|
||||
url(r"^file/(?P<popup>popup)?$", FileListView.as_view(), name="file_list"),
|
||||
url(
|
||||
r"^file/(?P<file_id>[0-9]+)/(?P<popup>popup)?$",
|
||||
FileView.as_view(),
|
||||
name="file_detail",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^file/(?P<file_id>[0-9]+)/edit/(?P<popup>popup)?$",
|
||||
FileEditView.as_view(),
|
||||
name="file_edit",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^file/(?P<file_id>[0-9]+)/prop/(?P<popup>popup)?$",
|
||||
FileEditPropView.as_view(),
|
||||
name="file_prop",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^file/(?P<file_id>[0-9]+)/delete/(?P<popup>popup)?$",
|
||||
FileDeleteView.as_view(),
|
||||
name="file_delete",
|
||||
),
|
||||
re_path(r"^file/moderation$", FileModerationView.as_view(), name="file_moderation"),
|
||||
re_path(
|
||||
url(r"^file/moderation$", FileModerationView.as_view(), name="file_moderation"),
|
||||
url(
|
||||
r"^file/(?P<file_id>[0-9]+)/moderate$",
|
||||
FileModerateView.as_view(),
|
||||
name="file_moderate",
|
||||
),
|
||||
re_path(r"^file/(?P<file_id>[0-9]+)/download$", send_file, name="download"),
|
||||
url(r"^file/(?P<file_id>[0-9]+)/download$", send_file, name="download"),
|
||||
# Page views
|
||||
re_path(r"^page/$", PageListView.as_view(), name="page_list"),
|
||||
re_path(r"^page/create$", PageCreateView.as_view(), name="page_new"),
|
||||
re_path(
|
||||
url(r"^page/$", PageListView.as_view(), name="page_list"),
|
||||
url(r"^page/create$", PageCreateView.as_view(), name="page_new"),
|
||||
url(
|
||||
r"^page/(?P<page_id>[0-9]*)/delete$",
|
||||
PageDeleteView.as_view(),
|
||||
name="page_delete",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/edit$",
|
||||
PageEditView.as_view(),
|
||||
name="page_edit",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/prop$",
|
||||
PagePropView.as_view(),
|
||||
name="page_prop",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/hist$",
|
||||
PageHistView.as_view(),
|
||||
name="page_hist",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/rev/(?P<rev>[0-9]+)/",
|
||||
PageRevView.as_view(),
|
||||
name="page_rev",
|
||||
),
|
||||
re_path(
|
||||
url(
|
||||
r"^page/(?P<page_name>([/a-zA-Z0-9][a-zA-Z0-9._-]*[a-zA-Z0-9])+)/$",
|
||||
PageView.as_view(),
|
||||
name="page",
|
||||
|
|
|
@ -189,7 +189,9 @@ def doku_to_markdown(text):
|
|||
except:
|
||||
new_text.append("> " * quote_level)
|
||||
line = line.replace(quote.group(0), "")
|
||||
final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration
|
||||
final_quote_level = (
|
||||
quote_level
|
||||
) # Store quote_level to use at the end, since it will be modified during quit iteration
|
||||
final_newline = False
|
||||
for quote in quit: # Quit quotes (support multiple at a time)
|
||||
line = line.replace(quote.group(0), "")
|
||||
|
@ -238,7 +240,9 @@ def bbcode_to_markdown(text):
|
|||
except:
|
||||
new_text.append("> " * quote_level)
|
||||
line = line.replace(quote.group(0), "")
|
||||
final_quote_level = quote_level # Store quote_level to use at the end, since it will be modified during quit iteration
|
||||
final_quote_level = (
|
||||
quote_level
|
||||
) # Store quote_level to use at the end, since it will be modified during quit iteration
|
||||
final_newline = False
|
||||
for quote in quit: # Quit quotes (support multiple at a time)
|
||||
line = line.replace(quote.group(0), "")
|
||||
|
|
|
@ -48,7 +48,7 @@ from core.models import Group
|
|||
from core.views.forms import LoginForm
|
||||
|
||||
|
||||
def forbidden(request, exception):
|
||||
def forbidden(request):
|
||||
try:
|
||||
return HttpResponseForbidden(
|
||||
render(
|
||||
|
@ -71,7 +71,7 @@ def forbidden(request, exception):
|
|||
)
|
||||
|
||||
|
||||
def not_found(request, exception):
|
||||
def not_found(request):
|
||||
return HttpResponseNotFound(render(request, "core/404.jinja"))
|
||||
|
||||
|
||||
|
@ -81,122 +81,32 @@ def internal_servor_error(request):
|
|||
|
||||
|
||||
def can_edit_prop(obj, user):
|
||||
"""
|
||||
:param obj: Object to test for permission
|
||||
:param user: core.models.User to test permissions against
|
||||
:return: if user is authorized to edit object properties
|
||||
:rtype: bool
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if not can_edit_prop(self.object ,request.user):
|
||||
raise PermissionDenied
|
||||
|
||||
"""
|
||||
if obj is None or user.is_owner(obj):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def can_edit(obj, user):
|
||||
"""
|
||||
:param obj: Object to test for permission
|
||||
:param user: core.models.User to test permissions against
|
||||
:return: if user is authorized to edit object
|
||||
:rtype: bool
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if not can_edit(self.object ,request.user):
|
||||
raise PermissionDenied
|
||||
|
||||
"""
|
||||
if obj is None or user.can_edit(obj):
|
||||
return True
|
||||
return can_edit_prop(obj, user)
|
||||
|
||||
|
||||
def can_view(obj, user):
|
||||
"""
|
||||
:param obj: Object to test for permission
|
||||
:param user: core.models.User to test permissions against
|
||||
:return: if user is authorized to see object
|
||||
:rtype: bool
|
||||
|
||||
:Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
if not can_view(self.object ,request.user):
|
||||
raise PermissionDenied
|
||||
|
||||
"""
|
||||
if obj is None or user.can_view(obj):
|
||||
return True
|
||||
return can_edit(obj, user)
|
||||
|
||||
|
||||
class GenericContentPermissionMixinBuilder(View):
|
||||
"""
|
||||
Used to build permission mixins
|
||||
This view protect any child view that would be showing an object that is restricted based
|
||||
on two properties
|
||||
|
||||
:prop permission_function: function to test permission with, takes an object and an user an return a bool
|
||||
:prop raised_error: permission to be raised
|
||||
|
||||
:raises: raised_error
|
||||
"""
|
||||
|
||||
permission_function = lambda obj, user: False
|
||||
raised_error = PermissionDenied
|
||||
|
||||
@classmethod
|
||||
def get_permission_function(cls, obj, user):
|
||||
return cls.permission_function(obj, user)
|
||||
|
||||
def dispatch(self, request, *arg, **kwargs):
|
||||
|
||||
if hasattr(self, "get_object") and callable(self.get_object):
|
||||
self.object = self.get_object()
|
||||
if not self.get_permission_function(self.object, request.user):
|
||||
raise self.raised_error
|
||||
return super(GenericContentPermissionMixinBuilder, self).dispatch(
|
||||
request, *arg, **kwargs
|
||||
)
|
||||
|
||||
# If we get here, it's a ListView
|
||||
|
||||
queryset = self.get_queryset()
|
||||
l_id = [o.id for o in queryset if self.get_permission_function(o, request.user)]
|
||||
if not l_id and queryset.count() != 0:
|
||||
raise self.raised_error
|
||||
self._get_queryset = self.get_queryset
|
||||
|
||||
def get_qs(self2):
|
||||
return self2._get_queryset().filter(id__in=l_id)
|
||||
|
||||
self.get_queryset = types.MethodType(get_qs, self)
|
||||
return super(GenericContentPermissionMixinBuilder, self).dispatch(
|
||||
request, *arg, **kwargs
|
||||
)
|
||||
|
||||
|
||||
class CanCreateMixin(View):
|
||||
"""
|
||||
This view is made to protect any child view that would create an object, and thus, that can not be protected by any
|
||||
of the following mixin
|
||||
|
||||
:raises: PermissionDenied
|
||||
"""
|
||||
|
||||
def dispatch(self, request, *arg, **kwargs):
|
||||
res = super(CanCreateMixin, self).dispatch(request, *arg, **kwargs)
|
||||
if not request.user.is_authenticated:
|
||||
if not request.user.is_authenticated():
|
||||
raise PermissionDenied
|
||||
return res
|
||||
|
||||
|
@ -207,46 +117,95 @@ class CanCreateMixin(View):
|
|||
raise PermissionDenied
|
||||
|
||||
|
||||
class CanEditPropMixin(GenericContentPermissionMixinBuilder):
|
||||
class CanEditPropMixin(View):
|
||||
"""
|
||||
This view is made to protect any child view that would be showing some properties of an object that are restricted
|
||||
to only the owner group of the given object.
|
||||
In other word, you can make a view with this view as parent, and it would be retricted to the users that are in the
|
||||
object's owner_group
|
||||
|
||||
:raises: PermissionDenied
|
||||
"""
|
||||
|
||||
permission_function = can_edit_prop
|
||||
def dispatch(self, request, *arg, **kwargs):
|
||||
try:
|
||||
self.object = self.get_object()
|
||||
if can_edit_prop(self.object, request.user):
|
||||
return super(CanEditPropMixin, self).dispatch(request, *arg, **kwargs)
|
||||
return forbidden(request)
|
||||
except:
|
||||
pass
|
||||
# If we get here, it's a ListView
|
||||
l_id = [o.id for o in self.get_queryset() if can_edit_prop(o, request.user)]
|
||||
if not l_id and self.get_queryset().count() != 0:
|
||||
raise PermissionDenied
|
||||
self._get_queryset = self.get_queryset
|
||||
|
||||
def get_qs(self2):
|
||||
return self2._get_queryset().filter(id__in=l_id)
|
||||
|
||||
self.get_queryset = types.MethodType(get_qs, self)
|
||||
return super(CanEditPropMixin, self).dispatch(request, *arg, **kwargs)
|
||||
|
||||
|
||||
class CanEditMixin(GenericContentPermissionMixinBuilder):
|
||||
class CanEditMixin(View):
|
||||
"""
|
||||
This view makes exactly the same thing as its direct parent, but checks the group on the edit_groups field of the
|
||||
object
|
||||
|
||||
:raises: PermissionDenied
|
||||
"""
|
||||
|
||||
permission_function = can_edit
|
||||
def dispatch(self, request, *arg, **kwargs):
|
||||
try:
|
||||
self.object = self.get_object()
|
||||
if can_edit(self.object, request.user):
|
||||
return super(CanEditMixin, self).dispatch(request, *arg, **kwargs)
|
||||
return forbidden(request)
|
||||
except:
|
||||
pass
|
||||
# If we get here, it's a ListView
|
||||
l_id = [o.id for o in self.get_queryset() if can_edit(o, request.user)]
|
||||
if not l_id and self.get_queryset().count() != 0:
|
||||
raise PermissionDenied
|
||||
self._get_queryset = self.get_queryset
|
||||
|
||||
def get_qs(self2):
|
||||
return self2._get_queryset().filter(id__in=l_id)
|
||||
|
||||
self.get_queryset = types.MethodType(get_qs, self)
|
||||
return super(CanEditMixin, self).dispatch(request, *arg, **kwargs)
|
||||
|
||||
|
||||
class CanViewMixin(GenericContentPermissionMixinBuilder):
|
||||
class CanViewMixin(View):
|
||||
"""
|
||||
This view still makes exactly the same thing as its direct parent, but checks the group on the view_groups field of
|
||||
the object
|
||||
|
||||
:raises: PermissionDenied
|
||||
"""
|
||||
|
||||
permission_function = can_view
|
||||
def dispatch(self, request, *arg, **kwargs):
|
||||
|
||||
try:
|
||||
self.object = self.get_object()
|
||||
if can_view(self.object, request.user):
|
||||
return super(CanViewMixin, self).dispatch(request, *arg, **kwargs)
|
||||
return forbidden(request)
|
||||
except:
|
||||
pass
|
||||
# If we get here, it's a ListView
|
||||
queryset = self.get_queryset()
|
||||
|
||||
l_id = [o.id for o in queryset if can_view(o, request.user)]
|
||||
if not l_id and queryset.count() != 0:
|
||||
raise PermissionDenied
|
||||
self._get_queryset = self.get_queryset
|
||||
|
||||
def get_qs(self2):
|
||||
return self2._get_queryset().filter(id__in=l_id)
|
||||
|
||||
self.get_queryset = types.MethodType(get_qs, self)
|
||||
return super(CanViewMixin, self).dispatch(request, *arg, **kwargs)
|
||||
|
||||
|
||||
class FormerSubscriberMixin(View):
|
||||
"""
|
||||
This view check if the user was at least an old subscriber
|
||||
|
||||
:raises: PermissionDenied
|
||||
"""
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
|
@ -255,19 +214,6 @@ class FormerSubscriberMixin(View):
|
|||
return super(FormerSubscriberMixin, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class UserIsLoggedMixin(View):
|
||||
"""
|
||||
This view check if the user is logged
|
||||
|
||||
:raises: PermissionDenied
|
||||
"""
|
||||
|
||||
def dispatch(self, request, *args, **kwargs):
|
||||
if request.user.is_anonymous:
|
||||
raise PermissionDenied
|
||||
return super(UserIsLoggedMixin, self).dispatch(request, *args, **kwargs)
|
||||
|
||||
|
||||
class TabedViewMixin(View):
|
||||
"""
|
||||
This view provide the basic functions for displaying tabs in the template
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#
|
||||
|
||||
# This file contains all the views that concern the page model
|
||||
from django.shortcuts import redirect, get_object_or_404
|
||||
from django.shortcuts import redirect
|
||||
from django.views.generic import ListView, DetailView, TemplateView
|
||||
from django.views.generic.edit import UpdateView, FormMixin, DeleteView
|
||||
from django.views.generic.detail import SingleObjectMixin
|
||||
|
@ -32,7 +32,7 @@ from django.conf import settings
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.http import HttpResponse
|
||||
from wsgiref.util import FileWrapper
|
||||
from django.urls import reverse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django import forms
|
||||
|
||||
|
@ -51,7 +51,9 @@ def send_file(request, file_id, file_class=SithFile, file_attr="file"):
|
|||
memory at once. The FileWrapper will turn the file object into an
|
||||
iterator for chunks of 8KB.
|
||||
"""
|
||||
f = get_object_or_404(file_class, id=file_id)
|
||||
f = file_class.objects.filter(id=file_id).first()
|
||||
if f is None or not f.file:
|
||||
return not_found(request)
|
||||
if not (
|
||||
can_view(f, request.user)
|
||||
or (
|
||||
|
@ -109,7 +111,7 @@ class AddFilesForm(forms.Form):
|
|||
owner=owner,
|
||||
is_folder=False,
|
||||
mime_type=f.content_type,
|
||||
size=f.size,
|
||||
size=f._size,
|
||||
)
|
||||
try:
|
||||
new_file.clean()
|
||||
|
@ -289,7 +291,7 @@ class FileView(CanViewMixin, DetailView, FormMixin):
|
|||
self.form = self.get_form() # The form handle only the file upload
|
||||
files = request.FILES.getlist("file_field")
|
||||
if (
|
||||
request.user.is_authenticated
|
||||
request.user.is_authenticated()
|
||||
and request.user.can_edit(self.object)
|
||||
and self.form.is_valid()
|
||||
):
|
||||
|
|
|
@ -27,7 +27,7 @@ from django import forms
|
|||
from django.conf import settings
|
||||
from django.db import transaction
|
||||
from django.templatetags.static import static
|
||||
from django.urls import reverse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.forms import (
|
||||
CheckboxSelectMultiple,
|
||||
|
@ -55,22 +55,40 @@ from PIL import Image
|
|||
# Widgets
|
||||
|
||||
|
||||
class SelectSingle(Select):
|
||||
def render(self, name, value, attrs=None):
|
||||
if attrs:
|
||||
attrs["class"] = "select_single"
|
||||
else:
|
||||
attrs = {"class": "select_single"}
|
||||
return super(SelectSingle, self).render(name, value, attrs)
|
||||
|
||||
|
||||
class SelectMultiple(Select):
|
||||
def render(self, name, value, attrs=None):
|
||||
if attrs:
|
||||
attrs["class"] = "select_multiple"
|
||||
else:
|
||||
attrs = {"class": "select_multiple"}
|
||||
return super(SelectMultiple, self).render(name, value, attrs)
|
||||
|
||||
|
||||
class SelectDateTime(DateTimeInput):
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
def render(self, name, value, attrs=None):
|
||||
if attrs:
|
||||
attrs["class"] = "select_datetime"
|
||||
else:
|
||||
attrs = {"class": "select_datetime"}
|
||||
return super(SelectDateTime, self).render(name, value, attrs, renderer)
|
||||
return super(SelectDateTime, self).render(name, value, attrs)
|
||||
|
||||
|
||||
class SelectDate(DateInput):
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
def render(self, name, value, attrs=None):
|
||||
if attrs:
|
||||
attrs["class"] = "select_date"
|
||||
else:
|
||||
attrs = {"class": "select_date"}
|
||||
return super(SelectDate, self).render(name, value, attrs, renderer)
|
||||
return super(SelectDate, self).render(name, value, attrs)
|
||||
|
||||
|
||||
class MarkdownInput(Textarea):
|
||||
|
@ -109,7 +127,7 @@ class MarkdownInput(Textarea):
|
|||
|
||||
|
||||
class SelectFile(TextInput):
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
def render(self, name, value, attrs=None):
|
||||
if attrs:
|
||||
attrs["class"] = "select_file"
|
||||
else:
|
||||
|
@ -117,7 +135,7 @@ class SelectFile(TextInput):
|
|||
output = (
|
||||
'%(content)s<div name="%(name)s" class="choose_file_widget" title="%(title)s"></div>'
|
||||
% {
|
||||
"content": super(SelectFile, self).render(name, value, attrs, renderer),
|
||||
"content": super(SelectFile, self).render(name, value, attrs),
|
||||
"title": _("Choose file"),
|
||||
"name": name,
|
||||
}
|
||||
|
@ -133,7 +151,7 @@ class SelectFile(TextInput):
|
|||
|
||||
|
||||
class SelectUser(TextInput):
|
||||
def render(self, name, value, attrs=None, renderer=None):
|
||||
def render(self, name, value, attrs=None):
|
||||
if attrs:
|
||||
attrs["class"] = "select_user"
|
||||
else:
|
||||
|
@ -141,7 +159,7 @@ class SelectUser(TextInput):
|
|||
output = (
|
||||
'%(content)s<div name="%(name)s" class="choose_user_widget" title="%(title)s"></div>'
|
||||
% {
|
||||
"content": super(SelectUser, self).render(name, value, attrs, renderer),
|
||||
"content": super(SelectUser, self).render(name, value, attrs),
|
||||
"title": _("Choose user"),
|
||||
"name": name,
|
||||
}
|
||||
|
@ -284,7 +302,7 @@ class UserProfileForm(forms.ModelForm):
|
|||
owner=self.instance,
|
||||
is_folder=False,
|
||||
mime_type=f.content_type,
|
||||
size=f.size,
|
||||
size=f._size,
|
||||
moderator=self.instance,
|
||||
is_moderated=True,
|
||||
)
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
from django.views.generic.edit import UpdateView, CreateView, DeleteView
|
||||
from django.views.generic import ListView
|
||||
from django.views.generic.edit import FormView
|
||||
from django.urls import reverse_lazy
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django import forms
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
#
|
||||
|
||||
# This file contains all the views that concern the page model
|
||||
from django.urls import reverse_lazy
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.views.generic import ListView, DetailView
|
||||
from django.views.generic.edit import UpdateView, CreateView, DeleteView
|
||||
from django.forms.models import modelform_factory
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
#
|
||||
# Copyright 2016,2017
|
||||
# - Skia <skia@libskia.so>
|
||||
# - Sli <antoine@bartuccio.fr>
|
||||
#
|
||||
# Ce fichier fait partie du site de l'Association des Étudiants de l'UTBM,
|
||||
# http://ae.utbm.fr.
|
||||
|
@ -41,9 +40,11 @@ from club.models import Club
|
|||
|
||||
|
||||
def index(request, context=None):
|
||||
from com.views import NewsListView
|
||||
if request.user.is_authenticated():
|
||||
from com.views import NewsListView
|
||||
|
||||
return NewsListView.as_view()(request)
|
||||
return NewsListView.as_view()(request)
|
||||
return render(request, "core/index.jinja")
|
||||
|
||||
|
||||
class NotificationList(ListView):
|
||||
|
@ -51,9 +52,8 @@ class NotificationList(ListView):
|
|||
template_name = "core/notification_list.jinja"
|
||||
|
||||
def get_queryset(self):
|
||||
# TODO: Bulk update in django 2.2
|
||||
if "see_all" in self.request.GET.keys():
|
||||
for n in self.request.user.notifications.filter(viewed=False):
|
||||
for n in self.request.user.notifications.all():
|
||||
n.viewed = True
|
||||
n.save()
|
||||
return self.request.user.notifications.order_by("-date")[:20]
|
||||
|
|
|
@ -26,9 +26,8 @@
|
|||
# This file contains all the views that concern the user model
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.contrib.auth import views
|
||||
from django.contrib.auth.forms import PasswordChangeForm
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.urls import reverse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.exceptions import PermissionDenied, ValidationError
|
||||
from django.http import Http404, HttpResponse
|
||||
from django.views.generic.edit import UpdateView
|
||||
|
@ -41,7 +40,7 @@ from django.views.generic import (
|
|||
)
|
||||
from django.forms.models import modelform_factory
|
||||
from django.forms import CheckboxSelectMultiple
|
||||
from django.urls import reverse_lazy
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.template.response import TemplateResponse
|
||||
from django.conf import settings
|
||||
from django.views.generic.dates import YearMixin, MonthMixin
|
||||
|
@ -53,7 +52,6 @@ from core.views import (
|
|||
CanViewMixin,
|
||||
CanEditMixin,
|
||||
CanEditPropMixin,
|
||||
UserIsLoggedMixin,
|
||||
TabedViewMixin,
|
||||
QuickNotifMixin,
|
||||
)
|
||||
|
@ -70,31 +68,15 @@ from counter.views import StudentCardForm
|
|||
from trombi.views import UserTrombiForm
|
||||
|
||||
|
||||
class SithLoginView(views.LoginView):
|
||||
def login(request):
|
||||
"""
|
||||
The login View
|
||||
The login view
|
||||
|
||||
Needs to be improve with correct handling of form exceptions
|
||||
"""
|
||||
|
||||
template_name = "core/login.jinja"
|
||||
authentication_form = LoginForm
|
||||
form_class = PasswordChangeForm
|
||||
|
||||
|
||||
class SithPasswordChangeView(views.PasswordChangeView):
|
||||
"""
|
||||
Allows a user to change its password
|
||||
"""
|
||||
|
||||
template_name = "core/password_change.jinja"
|
||||
success_url = reverse_lazy("core:password_change_done")
|
||||
|
||||
|
||||
class SithPasswordChangeDoneView(views.PasswordChangeDoneView):
|
||||
"""
|
||||
Allows a user to change its password
|
||||
"""
|
||||
|
||||
template_name = "core/password_change_done.jinja"
|
||||
return views.login(
|
||||
request, template_name="core/login.jinja", authentication_form=LoginForm
|
||||
)
|
||||
|
||||
|
||||
def logout(request):
|
||||
|
@ -104,6 +86,26 @@ def logout(request):
|
|||
return views.logout_then_login(request)
|
||||
|
||||
|
||||
def password_change(request):
|
||||
"""
|
||||
Allows a user to change its password
|
||||
"""
|
||||
return views.password_change(
|
||||
request,
|
||||
template_name="core/password_change.jinja",
|
||||
post_change_redirect=reverse("core:password_change_done"),
|
||||
)
|
||||
|
||||
|
||||
def password_change_done(request):
|
||||
"""
|
||||
Allows a user to change its password
|
||||
"""
|
||||
return views.password_change_done(
|
||||
request, template_name="core/password_change_done.jinja"
|
||||
)
|
||||
|
||||
|
||||
def password_root_change(request, user_id):
|
||||
"""
|
||||
Allows a root user to change someone's password
|
||||
|
@ -125,39 +127,47 @@ def password_root_change(request, user_id):
|
|||
)
|
||||
|
||||
|
||||
class SithPasswordResetView(views.PasswordResetView):
|
||||
def password_reset(request):
|
||||
"""
|
||||
Allows someone to enter an email adresse for resetting password
|
||||
"""
|
||||
|
||||
template_name = "core/password_reset.jinja"
|
||||
email_template_name = "core/password_reset_email.jinja"
|
||||
success_url = reverse_lazy("core:password_reset_done")
|
||||
return views.password_reset(
|
||||
request,
|
||||
template_name="core/password_reset.jinja",
|
||||
email_template_name="core/password_reset_email.jinja",
|
||||
post_reset_redirect="core:password_reset_done",
|
||||
)
|
||||
|
||||
|
||||
class SithPasswordResetDoneView(views.PasswordResetDoneView):
|
||||
def password_reset_done(request):
|
||||
"""
|
||||
Confirm that the reset email has been sent
|
||||
"""
|
||||
|
||||
template_name = "core/password_reset_done.jinja"
|
||||
return views.password_reset_done(
|
||||
request, template_name="core/password_reset_done.jinja"
|
||||
)
|
||||
|
||||
|
||||
class SithPasswordResetConfirmView(views.PasswordResetConfirmView):
|
||||
def password_reset_confirm(request, uidb64=None, token=None):
|
||||
"""
|
||||
Provide a reset password form
|
||||
Provide a reset password formular
|
||||
"""
|
||||
|
||||
template_name = "core/password_reset_confirm.jinja"
|
||||
success_url = reverse_lazy("core:password_reset_complete")
|
||||
return views.password_reset_confirm(
|
||||
request,
|
||||
uidb64=uidb64,
|
||||
token=token,
|
||||
post_reset_redirect="core:password_reset_complete",
|
||||
template_name="core/password_reset_confirm.jinja",
|
||||
)
|
||||
|
||||
|
||||
class SithPasswordResetCompleteView(views.PasswordResetCompleteView):
|
||||
def password_reset_complete(request):
|
||||
"""
|
||||
Confirm the password has sucessfully been reset
|
||||
"""
|
||||
|
||||
template_name = "core/password_reset_complete.jinja"
|
||||
return views.password_reset_complete(
|
||||
request, template_name="core/password_reset_complete.jinja"
|
||||
)
|
||||
|
||||
|
||||
def register(request):
|
||||
|
@ -630,7 +640,7 @@ class UserUploadProfilePictView(CanEditMixin, DetailView):
|
|||
owner=self.object,
|
||||
is_folder=False,
|
||||
mime_type=f.content_type,
|
||||
size=f.size,
|
||||
size=f._size,
|
||||
)
|
||||
new_file.file.name = name
|
||||
new_file.save()
|
||||
|
@ -678,7 +688,7 @@ class UserUpdateProfileView(UserTabsMixin, CanEditMixin, UpdateView):
|
|||
files = request.FILES.items()
|
||||
self.form.process(files)
|
||||
if (
|
||||
request.user.is_authenticated
|
||||
request.user.is_authenticated()
|
||||
and request.user.can_edit(self.object)
|
||||
and self.form.is_valid()
|
||||
):
|
||||
|
@ -732,7 +742,7 @@ class UserPreferencesView(UserTabsMixin, CanEditMixin, UpdateView):
|
|||
kwargs = super(UserPreferencesView, self).get_context_data(**kwargs)
|
||||
if not hasattr(self.object, "trombi_user"):
|
||||
kwargs["trombi_form"] = UserTrombiForm()
|
||||
if hasattr(self.object, "customer"):
|
||||
if self.object.customer:
|
||||
kwargs["student_card_form"] = StudentCardForm()
|
||||
return kwargs
|
||||
|
||||
|
@ -752,7 +762,7 @@ class UserUpdateGroupView(UserTabsMixin, CanEditPropMixin, UpdateView):
|
|||
current_tab = "groups"
|
||||
|
||||
|
||||
class UserToolsView(QuickNotifMixin, UserTabsMixin, UserIsLoggedMixin, TemplateView):
|
||||
class UserToolsView(QuickNotifMixin, UserTabsMixin, TemplateView):
|
||||
"""
|
||||
Displays the logged user's tools
|
||||
"""
|
||||
|
|
|
@ -5,7 +5,6 @@ from django.db import migrations, models
|
|||
import accounting.models
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -43,14 +42,7 @@ class Migration(migrations.Migration):
|
|||
verbose_name="counter type",
|
||||
),
|
||||
),
|
||||
(
|
||||
"club",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="club.Club",
|
||||
related_name="counters",
|
||||
),
|
||||
),
|
||||
("club", models.ForeignKey(to="club.Club", related_name="counters")),
|
||||
(
|
||||
"edit_groups",
|
||||
models.ManyToManyField(
|
||||
|
@ -66,10 +58,7 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"user",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
primary_key=True,
|
||||
serialize=False,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
primary_key=True, serialize=False, to=settings.AUTH_USER_MODEL
|
||||
),
|
||||
),
|
||||
(
|
||||
|
@ -108,17 +97,13 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"counter",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="counter.Counter",
|
||||
related_name="permanencies",
|
||||
to="counter.Counter", related_name="permanencies"
|
||||
),
|
||||
),
|
||||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
related_name="permanencies",
|
||||
to=settings.AUTH_USER_MODEL, related_name="permanencies"
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -184,10 +169,7 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"club",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
verbose_name="club",
|
||||
to="club.Club",
|
||||
related_name="products",
|
||||
verbose_name="club", to="club.Club", related_name="products"
|
||||
),
|
||||
),
|
||||
(
|
||||
|
@ -286,24 +268,15 @@ class Migration(migrations.Migration):
|
|||
),
|
||||
(
|
||||
"counter",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="counter.Counter",
|
||||
related_name="refillings",
|
||||
),
|
||||
models.ForeignKey(to="counter.Counter", related_name="refillings"),
|
||||
),
|
||||
(
|
||||
"customer",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="counter.Customer",
|
||||
related_name="refillings",
|
||||
),
|
||||
models.ForeignKey(to="counter.Customer", related_name="refillings"),
|
||||
),
|
||||
(
|
||||
"operator",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
related_name="refillings_as_operator",
|
||||
),
|
||||
|
|
|
@ -4,7 +4,6 @@ from __future__ import unicode_literals
|
|||
from django.db import migrations, models
|
||||
from django.conf import settings
|
||||
import accounting.models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -36,7 +35,6 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"counter",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="counter.Counter",
|
||||
related_name="cash_summaries",
|
||||
verbose_name="counter",
|
||||
|
@ -45,7 +43,6 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"user",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
related_name="cash_summaries",
|
||||
verbose_name="user",
|
||||
|
@ -77,7 +74,6 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"cash_summary",
|
||||
models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="counter.CashRegisterSummary",
|
||||
related_name="items",
|
||||
verbose_name="cash summary",
|
||||
|
@ -90,7 +86,6 @@ class Migration(migrations.Migration):
|
|||
model_name="permanency",
|
||||
name="counter",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to="counter.Counter",
|
||||
related_name="permanencies",
|
||||
verbose_name="counter",
|
||||
|
@ -100,7 +95,6 @@ class Migration(migrations.Migration):
|
|||
model_name="permanency",
|
||||
name="user",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
to=settings.AUTH_USER_MODEL,
|
||||
related_name="permanencies",
|
||||
verbose_name="user",
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -14,10 +13,7 @@ class Migration(migrations.Migration):
|
|||
model_name="counter",
|
||||
name="club",
|
||||
field=models.ForeignKey(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
verbose_name="club",
|
||||
to="club.Club",
|
||||
related_name="counters",
|
||||
verbose_name="club", to="club.Club", related_name="counters"
|
||||
),
|
||||
),
|
||||
migrations.AlterField(
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
@ -33,7 +32,6 @@ class Migration(migrations.Migration):
|
|||
(
|
||||
"product",
|
||||
models.OneToOneField(
|
||||
on_delete=django.db.models.deletion.CASCADE,
|
||||
verbose_name="product",
|
||||
related_name="eticket",
|
||||
to="counter.Product",
|
||||
|
|
|
@ -26,7 +26,7 @@ from django.db import models
|
|||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from django.conf import settings
|
||||
from django.urls import reverse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.validators import MinLengthValidator
|
||||
from django.forms import ValidationError
|
||||
from django.utils.functional import cached_property
|
||||
|
@ -51,7 +51,7 @@ class Customer(models.Model):
|
|||
is used by other accounting classes as reference to the customer, rather than using User
|
||||
"""
|
||||
|
||||
user = models.OneToOneField(User, primary_key=True, on_delete=models.CASCADE)
|
||||
user = models.OneToOneField(User, primary_key=True)
|
||||
account_id = models.CharField(_("account id"), max_length=10, unique=True)
|
||||
amount = CurrencyField(_("amount"))
|
||||
recorded_products = models.IntegerField(_("recorded product"), default=0)
|
||||
|
@ -163,9 +163,7 @@ class Product(models.Model):
|
|||
icon = models.ImageField(
|
||||
upload_to="products", null=True, blank=True, verbose_name=_("icon")
|
||||
)
|
||||
club = models.ForeignKey(
|
||||
Club, related_name="products", verbose_name=_("club"), on_delete=models.CASCADE
|
||||
)
|
||||
club = models.ForeignKey(Club, related_name="products", verbose_name=_("club"))
|
||||
limit_age = models.IntegerField(_("limit age"), default=0)
|
||||
tray = models.BooleanField(_("tray price"), default=False)
|
||||
parent_product = models.ForeignKey(
|
||||
|
@ -211,9 +209,7 @@ class Product(models.Model):
|
|||
|
||||
class Counter(models.Model):
|
||||
name = models.CharField(_("name"), max_length=30)
|
||||
club = models.ForeignKey(
|
||||
Club, related_name="counters", verbose_name=_("club"), on_delete=models.CASCADE
|
||||
)
|
||||
club = models.ForeignKey(Club, related_name="counters", verbose_name=_("club"))
|
||||
products = models.ManyToManyField(
|
||||
Product, related_name="counters", verbose_name=_("products"), blank=True
|
||||
)
|
||||
|
@ -348,19 +344,12 @@ class Refilling(models.Model):
|
|||
Handle the refilling
|
||||
"""
|
||||
|
||||
counter = models.ForeignKey(
|
||||
Counter, related_name="refillings", blank=False, on_delete=models.CASCADE
|
||||
)
|
||||
counter = models.ForeignKey(Counter, related_name="refillings", blank=False)
|
||||
amount = CurrencyField(_("amount"))
|
||||
operator = models.ForeignKey(
|
||||
User,
|
||||
related_name="refillings_as_operator",
|
||||
blank=False,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
customer = models.ForeignKey(
|
||||
Customer, related_name="refillings", blank=False, on_delete=models.CASCADE
|
||||
User, related_name="refillings_as_operator", blank=False
|
||||
)
|
||||
customer = models.ForeignKey(Customer, related_name="refillings", blank=False)
|
||||
date = models.DateTimeField(_("date"))
|
||||
payment_method = models.CharField(
|
||||
_("payment method"),
|
||||
|
@ -478,10 +467,6 @@ class Selling(models.Model):
|
|||
return user.is_owner(self.counter) and self.payment_method != "CARD"
|
||||
|
||||
def can_be_viewed_by(self, user):
|
||||
if (
|
||||
not hasattr(self, "customer") or self.customer is None
|
||||
): # Customer can be set to Null
|
||||
return False
|
||||
return user == self.customer.user
|
||||
|
||||
def delete(self, *args, **kwargs):
|
||||
|
@ -494,7 +479,7 @@ class Selling(models.Model):
|
|||
event = self.product.eticket.event_title or _("Unknown event")
|
||||
subject = _("Eticket bought for the event %(event)s") % {"event": event}
|
||||
message_html = _(
|
||||
"You bought an eticket for the event %(event)s.\nYou can download it directly from this link %(eticket)s.\nYou can also retrieve all your e-tickets on your account page %(url)s."
|
||||
"You bought an eticket for the event %(event)s.\nYou can download it on this page %(url)s."
|
||||
) % {
|
||||
"event": event,
|
||||
"url": "".join(
|
||||
|
@ -506,23 +491,10 @@ class Selling(models.Model):
|
|||
"</a>",
|
||||
)
|
||||
),
|
||||
"eticket": "".join(
|
||||
(
|
||||
'<a href="',
|
||||
self.get_eticket_full_url(),
|
||||
'">',
|
||||
self.get_eticket_full_url(),
|
||||
"</a>",
|
||||
)
|
||||
),
|
||||
}
|
||||
message_txt = _(
|
||||
"You bought an eticket for the event %(event)s.\nYou can download it directly from this link %(eticket)s.\nYou can also retrieve all your e-tickets on your account page %(url)s."
|
||||
) % {
|
||||
"event": event,
|
||||
"url": self.customer.get_full_url(),
|
||||
"eticket": self.get_eticket_full_url(),
|
||||
}
|
||||
"You bought an eticket for the event %(event)s.\nYou can download it on this page %(url)s."
|
||||
) % {"event": event, "url": self.customer.get_full_url()}
|
||||
self.customer.user.email_user(subject, message_txt, html_message=message_html)
|
||||
|
||||
def save(self, allow_negative=False, *args, **kwargs):
|
||||
|
@ -585,6 +557,11 @@ class Selling(models.Model):
|
|||
start=sub.subscription_start,
|
||||
)
|
||||
sub.save()
|
||||
try:
|
||||
if self.product.eticket:
|
||||
self.send_mail_customer()
|
||||
except:
|
||||
pass
|
||||
if self.customer.user.preferences.notify_on_click:
|
||||
Notification(
|
||||
user=self.customer.user,
|
||||
|
@ -600,16 +577,6 @@ class Selling(models.Model):
|
|||
type="SELLING",
|
||||
).save()
|
||||
super(Selling, self).save(*args, **kwargs)
|
||||
try:
|
||||
# The product has no id until it's saved
|
||||
if self.product.eticket:
|
||||
self.send_mail_customer()
|
||||
except:
|
||||
pass
|
||||
|
||||
def get_eticket_full_url(self):
|
||||
eticket_url = reverse("counter:eticket_pdf", kwargs={"selling_id": self.id})
|
||||
return "".join(["https://", settings.SITH_URL, eticket_url])
|
||||
|
||||
|
||||
class Permanency(models.Model):
|
||||
|
@ -617,17 +584,9 @@ class Permanency(models.Model):
|
|||
This class aims at storing a traceability of who was barman where and when
|
||||
"""
|
||||
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
related_name="permanencies",
|
||||
verbose_name=_("user"),
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
user = models.ForeignKey(User, related_name="permanencies", verbose_name=_("user"))
|
||||
counter = models.ForeignKey(
|
||||
Counter,
|
||||
related_name="permanencies",
|
||||
verbose_name=_("counter"),
|
||||
on_delete=models.CASCADE,
|
||||
Counter, related_name="permanencies", verbose_name=_("counter")
|
||||
)
|
||||
start = models.DateTimeField(_("start date"))
|
||||
end = models.DateTimeField(_("end date"), null=True, db_index=True)
|
||||
|
@ -648,16 +607,10 @@ class Permanency(models.Model):
|
|||
|
||||
class CashRegisterSummary(models.Model):
|
||||
user = models.ForeignKey(
|
||||
User,
|
||||
related_name="cash_summaries",
|
||||
verbose_name=_("user"),
|
||||
on_delete=models.CASCADE,
|
||||
User, related_name="cash_summaries", verbose_name=_("user")
|
||||
)
|
||||
counter = models.ForeignKey(
|
||||
Counter,
|
||||
related_name="cash_summaries",
|
||||
verbose_name=_("counter"),
|
||||
on_delete=models.CASCADE,
|
||||
Counter, related_name="cash_summaries", verbose_name=_("counter")
|
||||
)
|
||||
date = models.DateTimeField(_("date"))
|
||||
comment = models.TextField(_("comment"), null=True, blank=True)
|
||||
|
@ -730,10 +683,7 @@ class CashRegisterSummary(models.Model):
|
|||
|
||||
class CashRegisterSummaryItem(models.Model):
|
||||
cash_summary = models.ForeignKey(
|
||||
CashRegisterSummary,
|
||||
related_name="items",
|
||||
verbose_name=_("cash summary"),
|
||||
on_delete=models.CASCADE,
|
||||
CashRegisterSummary, related_name="items", verbose_name=_("cash summary")
|
||||
)
|
||||
value = CurrencyField(_("value"))
|
||||
quantity = models.IntegerField(_("quantity"), default=0)
|
||||
|
@ -749,10 +699,7 @@ class Eticket(models.Model):
|
|||
"""
|
||||
|
||||
product = models.OneToOneField(
|
||||
Product,
|
||||
related_name="eticket",
|
||||
verbose_name=_("product"),
|
||||
on_delete=models.CASCADE,
|
||||
Product, related_name="eticket", verbose_name=_("product")
|
||||
)
|
||||
banner = models.ImageField(
|
||||
upload_to="etickets", null=True, blank=True, verbose_name=_("banner")
|
||||
|
@ -825,5 +772,4 @@ class StudentCard(models.Model):
|
|||
verbose_name=_("student cards"),
|
||||
null=False,
|
||||
blank=False,
|
||||
on_delete=models.CASCADE,
|
||||
)
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h4 id="click_interface">{{ counter }}</h4>
|
||||
<h4>{{ counter }}</h4>
|
||||
|
||||
<div id="user_info">
|
||||
<h5>{% trans %}Customer{% endtrans %}</h5>
|
||||
|
@ -127,14 +127,17 @@
|
|||
</div>
|
||||
<div id="products">
|
||||
<ul>
|
||||
{% for category in categories.keys() -%}
|
||||
<li><a href="#cat_{{ category|slugify }}">{{ category }}</a></li>
|
||||
{% for t in categories -%}
|
||||
{%- if counter.products.filter(product_type=t).exists() -%}
|
||||
<li><a href="#cat_{{ t|slugify }}">{{ t }}</a></li>
|
||||
{%- endif -%}
|
||||
{%- endfor %}
|
||||
</ul>
|
||||
{% for category in categories.keys() -%}
|
||||
<div id="cat_{{ category|slugify }}">
|
||||
<h5>{{ category }}</h5>
|
||||
{% for p in categories[category] -%}
|
||||
{% for t in categories -%}
|
||||
{%- if counter.products.filter(product_type=t).exists() -%}
|
||||
<div id="cat_{{ t|slugify }}">
|
||||
<h5>{{ t }}</h5>
|
||||
{% for p in counter.products.filter(product_type=t).all() -%}
|
||||
{% set file = None %}
|
||||
{% if p.icon %}
|
||||
{% set file = p.icon.url %}
|
||||
|
@ -145,20 +148,18 @@
|
|||
{{ add_product(p.id, prod, "form_button") }}
|
||||
{%- endfor %}
|
||||
</div>
|
||||
{%- endif -%}
|
||||
{%- endfor %}
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
document.getElementById("click_interface").scrollIntoView();
|
||||
</script>
|
||||
{{ super() }}
|
||||
<script>
|
||||
$( function() {
|
||||
var products = [
|
||||
{% for p in products -%}
|
||||
{% for p in counter.products.all() -%}
|
||||
{
|
||||
value: "{{ p.code }}",
|
||||
label: "{{ p.name }}",
|
||||
|
@ -202,3 +203,6 @@ $( function() {
|
|||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<h3>{% trans %}Eticket list{% endtrans %}</h3>
|
||||
<ul>
|
||||
{% for t in eticket_list %}
|
||||
<li><a href="{{ url('counter:edit_eticket', eticket_id=t.id) }}">{{ t }}</a> (ID: {{ t.product.id }} | Hash: <code>{{ t.secret }}</code>)</li>
|
||||
<li><a href="{{ url('counter:edit_eticket', eticket_id=t.id) }}">{{ t }}</a> (Hash: <code>{{ t.secret }}</code>)</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
{% else %}
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
import re
|
||||
|
||||
from django.test import TestCase
|
||||
from django.urls import reverse
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.core.management import call_command
|
||||
|
||||
from core.models import User
|
||||
|
@ -83,17 +83,6 @@ class CounterTest(TestCase):
|
|||
)
|
||||
|
||||
|
||||
class CounterStatsTest(TestCase):
|
||||
def setUp(self):
|
||||
call_command("populate")
|
||||
self.counter = Counter.objects.filter(id=2).first()
|
||||
|
||||
def test_unothorized_user_fail(self):
|
||||
# Test with not login user
|
||||
response = self.client.get(reverse("counter:stats", args=[self.counter.id]))
|
||||
self.assertTrue(response.status_code == 403)
|
||||
|
||||
|
||||
class BarmanConnectionTest(TestCase):
|
||||
def setUp(self):
|
||||
call_command("populate")
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue