Commit 40dd8e71 authored by okhin's avatar okhin 🚴

Fixing conflict

parents 52405ae9 785faa1f
Pipeline #2488 passed with stages
in 3 minutes and 7 seconds
......@@ -24,10 +24,6 @@ unit tests:
- echo "SECRET_KEY = '$(pwgen 20 1)'" >> ./project/settings/env.py
- ./manage.py migrate
- pytest apps/ --cov=apps/
artifacts:
paths:
- env/
expire_in: 1 day
tags: [preprod]
static build:
......@@ -42,8 +38,6 @@ static build:
- echo "SECRET_KEY = '$(pwgen 20 1)'" >> ./project/settings/env.py
- ./manage.py collectstatic
tags: [preprod]
dependencies:
- unit tests
artifacts:
paths:
- static/
......@@ -51,11 +45,11 @@ static build:
deploy preprod:
variables:
BASE_PATH: /srv/rp2/src/
VIRTUALENV: /srv/rp2/rp2-env
BASE_PATH: /srv/rp/rp-rp2
VIRTUALENV: /srv/rp/env
stage: deploy
script:
- rsync --exclude 'env' --exclude 'pip-cache' --exclude '.git' -r --chown g+rw ./ ${BASE_PATH}
- rsync --exclude 'env' --exclude 'pip-cache' --exclude '.git' -r --chown rp:www-data --chmod g+srw ./ ${BASE_PATH}
- source ${VIRTUALENV}/bin/activate
- pip install --upgrade pip
- pip install --upgrade -r requirements.txt
......
......@@ -41,6 +41,11 @@ the FQDN of the RP before being able to run in production
If DEBUG is defined as True, ALLOWED_HOSTS is set to allow connections
from any hosts.
Groups have to be initialized with the following command:
```sh
$ python manage.py init_groups
```
## Database
You can run migrations with :
......
from django.core.management.base import BaseCommand
from django.contrib.auth.models import Group, Permission
from django.contrib.auth.models import User, Group, Permission
groups = ["jedi", "padawan"]
groups = ["droid", "jedi", "padawan"]
permissions = {
"droid": [],
"jedi": [
"can_change_status", "can_change_priority", "can_vote", "can_edit"
"can_change_status", "can_change_priority", "can_vote", "can_edit", "can_edit_users", "can_delete_users"
],
"padawan": ["can_vote", "add_article"]
}
......@@ -20,3 +21,7 @@ class Command(BaseCommand):
new_group, created = Group.objects.get_or_create(name=g)
for p in permissions[g]:
new_group.permissions.add(Permission.objects.get(codename=p))
users = User.objects.all()
for user in users:
user.groups.add(Group.objects.get(name="padawan"))
from django import forms
from django.contrib.auth.models import Group
class TagMultipleChoiceField(forms.ModelMultipleChoiceField):
def prepare_value(self, value):
......@@ -15,3 +15,9 @@ class TagMultipleChoiceField(forms.ModelMultipleChoiceField):
self.validate(value)
self.run_validators(value)
return value
class SignupForm(forms.Form):
def signup(self, request, user):
group = Group.objects.get(name='padawan')
user.groups.add(group)
user.save()
from django.db import migrations
from django.contrib.auth.models import Group
groups = ['Jedi', 'Padawan', 'Droid']
def add_groups(apps, schema_editor):
for i in groups:
group = Group(name=i)
group.save()
def remove_groups(apps, schema_editor):
for i in groups:
Group.objects.filter(name=i).delete()
class Migration(migrations.Migration):
dependencies = [
('rp', '0016_auto_20170617_0929'),
]
operations = [
migrations.RunPython(add_groups, reverse_code=remove_groups),
]
from django.db import migrations
from django.contrib.auth.models import Group, User
def remove_groups(apps, schema_editor):
groups = ['Jedi', 'Padawan', 'Droid']
for i in groups:
Group.objects.filter(name=i).delete()
class Migration(migrations.Migration):
dependencies = [
('rp', '0017_groups'),
]
operations = [
migrations.RunPython(remove_groups),
]
......@@ -150,83 +150,4 @@
</div>
</div>
</div>
<script>
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
function call_upvote(id) {
$.post("/api/articles/" + id + "/upvote/", function response(data) {
$("#count_up_" + id).text(data.und_score_up);
});
}
function call_downvote(id) {
$.post("/api/articles/" + id + "/downvote/", function response(data) {
$("#count_down_" + id).text(data.und_score_down);
});
}
function clean_row(id) {
$("#row_" + id).hide();
$("#row_empty_" + id).hide();
$("#row_tags_" + id).hide();
}
function call_recover(id) {
$.post("/api/articles/" + id + "/recover/", function response(data) {
clean_row(id);
});
}
function call_reject(id) {
$.post("/api/articles/" + id + "/reject/", function response(data) {
clean_row(id);
});
}
function call_publish(id) {
$.post("/api/articles/" + id + "/publish/", function response(data) {
clean_row(id);
});
}
function call_priority(id, flag) {
if(flag) {
var url = "/api/articles/" + id + "/set_priority/";
} else {
var url = "/api/articles/" + id + "/unset_priority/";
}
$.post(url, function response(data) {
$("#priority_" + id).toggleClass("fa-star").toggleClass("fa-star-o");
});
}
</script>
{% endblock %}
from django.contrib.auth.decorators import login_required
from django.conf.urls import url
from rp.views.articles import ArticleListFlux, ArticleEdit, ArticleDetailView, ArticleList
......@@ -21,27 +20,27 @@ urlpatterns = [
),
url(
r"^article/list/(?P<filter_view>\w+)",
login_required(ArticleListFlux.as_view()),
ArticleListFlux.as_view(),
name="article-list"
),
url(
r"^article/list",
login_required(ArticleListFlux.as_view()),
ArticleListFlux.as_view(),
name="article-list"
),
url(
r"^article/edit/(?P<pk>\d+)",
login_required(ArticleEdit.as_view()),
ArticleEdit.as_view(),
name="article-edit"
),
url(
r"^article/view/(?P<pk>\d+)",
login_required(ArticleDetailView.as_view()),
ArticleDetailView.as_view(),
name="article-view"
),
url(
r"^article/preview/(?P<pk>\d+)",
login_required(ArticleDetailView.as_view(preview=True)),
ArticleDetailView.as_view(preview=True),
name="article-preview"
)
]
......@@ -57,7 +57,7 @@ class ArticleList(ListView):
return context
class ArticleListFlux(UDList):
class ArticleListFlux(LoginRequiredMixin, UDList):
model = Article
paginate_by = 10
......@@ -78,7 +78,7 @@ class ArticleListFlux(UDList):
return context
class ArticleDetailView(DetailView):
class ArticleDetailView(LoginRequiredMixin, DetailView):
model = Article
preview = False
......@@ -90,7 +90,7 @@ class ArticleDetailView(DetailView):
class ArticleEdit(PermissionRequiredMixin, UpdateView):
model = Article
permission_required = 'can_edit'
permission_required = 'rp.can_edit'
fields = ['screenshot', 'url', 'lang', 'title', 'tags', 'extracts']
success_url = reverse_lazy("rp:article-list")
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2017-11-29 11:30
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('userprofile', '0001_initial'),
]
operations = [
migrations.AlterModelOptions(
name='profile',
options={'permissions': (('can_edit_users', 'Can edit users'),), 'verbose_name': 'User'},
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.2 on 2018-01-09 16:42
from __future__ import unicode_literals
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('userprofile', '0002_auto_20171129_1130'),
]
operations = [
migrations.AlterModelOptions(
name='profile',
options={'permissions': (('can_edit_users', 'Can edit users'), ('can_delete_users', 'Can delete users')), 'verbose_name': 'User'},
),
]
......@@ -39,6 +39,10 @@ class Profile(models.Model):
class Meta:
verbose_name = _("User")
app_label = "userprofile"
permissions = (
("can_edit_users", "Can edit users",),
("can_delete_users", "Can delete users",),
)
def __str__(self):
"""Returns user username"""
......
{% extends "base.html" %}
{% block content %}
<h3>Delete user <strong>{{ user_delete.username }}</strong></h3>
<form action="" method="post">{% csrf_token %}
<p>Are you sure you want to delete user "<strong>{{ user_delete.username }}</strong>"?</p>
<input type="submit" value="Delete" />
</form>
{% endblock %}
{% extends "base.html" %}
{% block content-header %}
{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12 white-bg">
{% if is_paginated %}
<div class="pagination">
<span class="page-links">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}"><i class="fa fa-chevron-left" aria-hidden="true"></i></a>
{% endif %}
<span class="page-current">
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
</span>
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}"><i class="fa fa-chevron-right" aria-hidden="true"></i></a>
{% endif %}
</span>
</div>
{% endif %}
<table class="article-table table table-sm my-4">
<thead>
<tr>
<th>ID</th>
<th>Username</th>
<th>Email</th>
<th>Registration date</th>
<th>Role</th>
<th>Actions<img class="inline-image ml-2" role="img" src="{% static 'img/jedi.svg' %}" /></th>
</tr>
</thead>
<tbody>
{% for user in object_list %}
<tr id="row_{{user.id}}">
<td>{{user.id}}</td>
<td>{{user.username}}</td>
<td>{{user.email}}</td>
<td>{{user.date_joined | date:'d/m/Y - H:i:s'}}</td>
<td>{{user.groups.last.name}}</td>
<td>
<a href="{% url 'users:edit' user.id %}">
<i class="fa fa-fw fa-pencil" aria-hidden="true"></i> Edit
</a>
<br />
<a href="{% url 'users:delete' user.id %}">
<i class="fa fa-fw fa-times" aria-hidden="true"></i> Delete
</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
{% extends "base.html" %}
{% block content %}
<h3>Edit user <strong>{{ user_edit.username }}</strong></h3>
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Update" />
</form>
{% endblock %}
from django.conf.urls import url
from userprofile.views.users import UserDeleteView, UserEditView, UserListView
urlpatterns = [
url(
r"^list",
UserListView.as_view(),
name="list"
),
url(
r"^edit/(?P<pk>\d+)",
UserEditView.as_view(),
name="edit"
),
url(
r"^delete/(?P<pk>\d+)",
UserDeleteView.as_view(),
name="delete"
)
]
from django.shortcuts import render
# Create your views here.
from django.urls import reverse_lazy
from django.contrib.auth.models import User
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic.list import ListView
from django.views.generic.edit import DeleteView, UpdateView
class UserListView(LoginRequiredMixin, PermissionRequiredMixin, ListView):
model = User
paginate_by = 20
template_name = 'user/user_list.html'
permission_required = 'userprofile.can_edit_users'
def get_queryset(self):
qs = super().get_queryset()
return qs
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
return context
class UserEditView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
model = User
permission_required = 'userprofile.can_edit_users'
template_name = 'user/user_update_form.html'
context_object_name = 'user_edit'
fields = ['groups']
success_url = reverse_lazy("users:list")
class UserDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView):
model = User
permission_required = 'userprofile.can_delete_users'
template_name = 'user/user_delete_confirm.html'
context_object_name = 'user_delete'
success_url = reverse_lazy("users:list")
......@@ -41,3 +41,4 @@ AUTH_PASSWORD_VALIDATORS = [
# Disable two steps logout
ACCOUNT_LOGOUT_ON_GET = True
ACCOUNT_SIGNUP_FORM_CLASS = "rp.forms.SignupForm"
......@@ -22,6 +22,7 @@ urlpatterns = [
url(r"^feeds/", include("rp.feeds.urls", namespace="feeds")),
url(r"^rp/", include("rp.urls", namespace="rp")),
url(r'^accounts/', include('allauth.urls')),
url(r"^users/", include('userprofile.urls', namespace="users"))
]
if settings.DEBUG:
......
......@@ -16,3 +16,6 @@ require('simplemde/dist/simplemde.min.css')
// Local styles
import styles from './admin.css';
// RP js
require('./rp.js')
$(function () {
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie !== '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});
window.call_upvote = function(id) {
$.post("/api/articles/" + id + "/upvote/", function response(data) {
$("#count_up_" + id).text(data.und_score_up);
});
}
window.call_downvote = function(id) {
$.post("/api/articles/" + id + "/downvote/", function response(data) {
$("#count_down_" + id).text(data.und_score_down);
});
}
window.clean_row = function(id) {
$("#row_" + id).hide();
$("#row_empty_" + id).hide();
$("#row_tags_" + id).hide();
}
window.call_recover = function(id) {
$.post("/api/articles/" + id + "/recover/", function response(data) {
clean_row(id);
});
}
window.call_reject = function(id) {
$.post("/api/articles/" + id + "/reject/", function response(data) {
clean_row(id);
});
}
window.call_publish = function(id) {
$.post("/api/articles/" + id + "/publish/", function response(data) {
clean_row(id);
});
}
window.call_priority = function(id, flag) {
if(flag) {
var url = "/api/articles/" + id + "/set_priority/";
} else {
var url = "/api/articles/" + id + "/unset_priority/";
}
$.post(url, function response(data) {
$("#priority_" + id).toggleClass("fa-star").toggleClass("fa-star-o");
});
}
});
......@@ -16,7 +16,7 @@
<meta property="og:description" content="" />
<!-- Styles and scripts -->
<script src="{% static 'admin.js' %}" charset="utf-8"></script>
<script src="{% static 'admin.min.js' %}" charset="utf-8"></script>
<link rel="stylesheet" href="{% static 'app.bundle.css' %}">
</head>
<body>
......@@ -57,12 +57,17 @@
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link">
Vous êtes Padawan<img class="inline-image ml-2" role="img" src="{% static 'img/padawan.svg' %}" />
Vous êtes {{user.groups.last.name}}
{% if user.groups.last.name == "padawan" %}
<img class="inline-image ml-2" role="img" src="{% static 'img/padawan.svg' %}" />
{% elif user.groups.last.name == "jedi" %}
<img class="inline-image ml-2" role="img" src="{% static 'img/jedi.svg' %}" />
{% endif %}
</a>
</li>
</ul>
{% else %}
<span class="navbar-text ml-auto text-muted">For jedis by jedis</span>
<span class="navbar-text ml-auto text-muted">For jedi by jedi</span>
{% endif %}
</div>
</nav>
......
......@@ -23,9 +23,10 @@ module.exports = {
},
output: {
path: path.resolve(__dirname, 'static/dist'),
filename: "./admin.js"
filename: "./admin.min.js"
},
plugins: [
new ExtractTextPlugin('[name].bundle.css'),
new webpack.optimize.UglifyJsPlugin({minimize: true})
],
};
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment