Merge branch 'develop' into 'master'

Prod deploy

See merge request sahkoinsinoorikilta/vtmk/web2.0-backend!36
This commit is contained in:
Aarni Halinen
2021-03-30 17:08:29 +00:00
28 changed files with 5287 additions and 2175 deletions
+4 -5
View File
@@ -4,11 +4,10 @@
"jquery": true
},
"globals": {
"angular": 1,
"noty": 1,
"app": 1,
"_": 1,
"moment": 1
"angular": true,
"noty": true,
"_": true,
"moment": true
},
"extends": "eslint:recommended",
"rules": {
+4 -4
View File
@@ -6,7 +6,7 @@ stages:
- deploy
install:
image: node:12
image: node:14
stage: setup
script:
- npm ci
@@ -40,17 +40,17 @@ lint:py:
needs: []
script:
- pip install pycodestyle
- pycodestyle --config=setup.cfg --count .
- pycodestyle --config=pycodestyle.cfg --count .
lint:js:
image: node:alpine
image: node:14
stage: lint
needs: ["install"]
script:
- npm run lint:js
lint:md:
image: node:alpine
image: node:14
stage: lint
needs: ["install"]
script:
+1
View File
@@ -0,0 +1 @@
_
+5 -6
View File
@@ -1,9 +1,10 @@
#!/bin/bash
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
PURPLE='\033[0;35m'
NC='\033[0m' # No Color
. "${VIRTUAL_ENV}/bin/activate"
source "${VIRTUAL_ENV}/bin/activate"
if [ $? -ne 0 ]
then
@@ -14,10 +15,8 @@ fi
set -e
printf "${PURPLE}Running pre-push tests.${NC}\n"
printf "${PURPLE}npm tests...${NC}\n"
npm test
printf "${PURPLE}pycodestyle tests...${NC}\n"
pycodestyle .
printf "${PURPLE}linters...${NC}\n"
npm run lint
printf "${PURPLE}unit tests...${NC}\n"
python -Wall manage.py test --noinput
set +e
View File
-3
View File
@@ -1,3 +0,0 @@
from django.contrib import admin
# Register your models here.
View File
-3
View File
@@ -1,3 +0,0 @@
from django.db import models
# Create your models here.
@@ -1,124 +0,0 @@
body {
background-color: white;
font-family: monospace;
color: black;
}
#container{
position:relative;
width:95%;
margin-left:auto;
margin-right:auto;
height:100%;
overflow:hidden;
}
#upper{
background-size: contain;
background-repeat: no-repeat;
background-position: bottom center;
background-image: url("/static/coffee_scale/img/smokes.png");
transform-origin: bottom;
animation: smokes 8s ease-in-out 0s infinite;
opacity:0;
height:40%;
}
#lower{
position:relative;
background-size: contain;
background-repeat: no-repeat;
background-position: top center;
background-image: url("/static/coffee_scale/img/coffeecup3.png");
height:60%;
}
#scale{
position:absolute;
top:80%;
width:90%;
height:10%;
margin: 0% 5% 0% 5%;
background: lightgrey;
border-radius: 10px;
overflow:hidden;
}
#scale2{
width: 0%;
transition: width 2s;
height:100%;
background: green;
border-radius: 10px;
}
.brewtime{
text-align:right;
position:absolute;
right:0px;
z-index:5;
font-size:10vw;
}
#address{
text-align:left;
position:absolute;
left:0px;
z-index:5;
font-size:4vw;
color: #333;
}
.layertwo{
display: None;
}
noscript{
color:red;
}
.text{
color:green;
position:absolute;
top:50%;
left:50%;
}
.brewing{
animation: brewing 5s ease-in-out 0s infinite;
}
.hurry{
color:red !important;
}
.unknown{
color:orange !important;
animation: unknown 5s ease-in-out 0s infinite;
}
.friday{
animation: friday 20s ease-in-out 0s infinite;
}
.normal{
animation: normal 1000s ease-in-out 0s infinite;
}
.coffeeready{
animation: coffeeready 10s ease-in-out 0s;
}
@keyframes smokes {
0% {transform: skewX(-10deg);}
50% {transform: skewX(10deg);}
100% {transform: skewX(-10deg);}
}
@keyframes brewing {
0% {color:green;}
50% {color: transparent;}
100% {color:green;}
}
@keyframes coffeeready {
0% {background-color:white;}
25% {background-color:rgb(100, 255, 100);}
50% {background-color:white;}
75% {background-color:rgb(100, 255, 100);}
100% {background-color:white;}
}
@keyframes unknown {
0%,40% {transform: rotate(0deg);}
60%,100% {transform: rotate(360deg);}
}
@keyframes friday {
0% {transform: rotate(0deg);}
100% {transform: rotate(360deg);}
}
@keyframes normal {
0%,49% {transform: rotate(0deg);}
50%,100% {transform: rotate(360deg);}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

@@ -1,183 +0,0 @@
//Inner state
var lastBrew = new Date(0);
var brewing = false;
var backoff = 2000;
//MQTT client config
var username = "coffee-user-"+ Math.random();
// eslint-disable-next-line no-undef
var client = new Paho.MQTT.Client("sika.sahkoinsinoorikilta.fi", 9001, username);
client.onMessageArrived = function (message) {
// eslint-disable-next-line no-console
console.log("Topic: "+message.destinationName+" msg: "+message.payloadString);
var ev = new CustomEvent(message.destinationName, {'detail': message.payloadString});
window.dispatchEvent(ev);
}
function reconnect(responseObject){
if (responseObject.errorCode !== 0) {
console.log("connection lost! Reason: "+responseObject.errorMessage); // eslint-disable-line no-console
setTimeout(function(){
client.connect({onSuccess:onConnect, useSSL:true, onFailure: reconnect});
}, backoff);
}
}
function onConnect() {
console.log("MQTT connected"); // eslint-disable-line no-console
//set and reset reconnector
client.onConnectionLost = reconnect
// subscribe to topics
client.subscribe("sik/kiltahuone/kahvivaaka/cups");
client.subscribe("sik/kiltahuone/kahvivaaka/brewing");
client.subscribe("sik/kiltahuone/kahvivaaka/brewtime");
}
// data update and parse functions
function parseCups(ev){
var cups = parseFloat(ev.detail).toFixed(1)
function makeEvent(cups) {
return (String(cups) !== '-1.0')
? new CustomEvent("cupsChanged", {'detail': cups})
: new CustomEvent("cupsError", {'detail': 'Error: unable to fetch cups :('});
}
window.dispatchEvent(makeEvent(cups));
}
function updateCups(ev){
$("#text").text(ev.detail);
}
function showCupsError(ev) {
$('#text').text(ev.detail);
$('#text').css({
'font-size': '7vh',
'left': '0',
'top': '40%',
'width': '100%',
'text-align': 'center',
'color': 'red',
});
$('#lower').css({'background-image': 'none'});
}
function updateScale(ev){
$("#scale2").css({width: Math.min(ev.detail/9*100,100) + '%'});
}
function tick(){
var ev = new CustomEvent("tick", {'detail': new Date()});
window.dispatchEvent(ev);
}
function updateTime(ev){
var now = ev.detail;
$("#time").html(formatTime(now.getHours(),now.getMinutes(),now.getSeconds()));
}
function coffeeLowEffect(ev){
ev.detail <= 2 ? $("#text").addClass("hurry") : $("#text").removeClass("hurry");
}
function coffeeReadyEffect(){
$("body").addClass("coffeeready");
// autoclear animation class in 10s
setTimeout(function(){$("body").removeClass("coffeeready");}, 10000);
}
function hotEffect(ev){
var opa = Math.max(100 - ev.detail / 90000,0);
$("#upper").css({opacity: opa/100});
}
function brewAnimStart(){
$(".text").addClass("brewing");
$(".layerone").hide();
$(".layertwo").show();
}
function brewAnimEnd(){
$(".text").removeClass("brewing");
$(".layertwo").hide();
$(".layerone").show();
}
function brewNotifier(ev){
var new_brewing = parseInt(ev.detail);
if (new_brewing == 1 && brewing == 0){
window.dispatchEvent(new Event("brewStart"));
} else if (new_brewing == 0 && brewing == 1){
window.dispatchEvent(new Event("brewEnd"));
}
brewing = new_brewing;
}
function brewTimeParser(ev){
lastBrew = new Date(parseInt(ev.detail)*1000.0);
}
function updateBrewDiff(){
var now = new Date();
var timeDiff = Math.max(now.getTime() - lastBrew.getTime(), 0);
var eve = new CustomEvent("dtUpdate", {'detail': timeDiff});
window.dispatchEvent(eve);
}
function updateBrewTime(ev){
var timeDiff = ev.detail;
var timeStr;
if (timeDiff < 3600000){
timeStr = Math.round(timeDiff / 60000) + ' min'
} else if (timeDiff < 10000* 3600 * 1000){ // 1000h
timeStr = '~' + Math.round(timeDiff / 3600000 * 2) / 2 + ' h';
} else {
timeStr = "???"
}
$("#brewtime").html(timeStr);
}
// Helpers
function nToS(num){
return num < 10 ? "0" + num : "" + num;
}
function formatTime(hours, minutes, seconds){
return nToS(hours)+":"+nToS(minutes)+":"+nToS(seconds)
}
function resize(){
var w = $("#container").width();
var h = $("#container").height();
var s = w > h ? h : w;
var font = s * 0.8 * 0.38/Math.sqrt(3);
$(".text").css({ top: s*0.16-font/2 + 'px',
fontSize: font + 'px',
marginLeft: -font*3*3/10 + 'px'});
}
// Init everything
$(document).ready(function(){
client.connect({onSuccess:onConnect, useSSL:true, onFailure:reconnect});
//connect MQTT event listeners
window.addEventListener("sik/kiltahuone/kahvivaaka/cups", parseCups);
window.addEventListener("sik/kiltahuone/kahvivaaka/brewing", brewNotifier);
window.addEventListener("sik/kiltahuone/kahvivaaka/brewtime", brewTimeParser);
//connect other event listeners
window.addEventListener("cupsChanged", updateCups);
window.addEventListener("cupsChanged", coffeeLowEffect);
window.addEventListener("cupsChanged", updateScale);
window.addEventListener("cupsChanged", resize);
window.addEventListener("cupsError", showCupsError);
window.addEventListener("cupsError", coffeeLowEffect);
window.addEventListener("cupsError", updateScale);
window.addEventListener("brewStart", brewAnimStart);
window.addEventListener("brewEnd", brewAnimEnd);
window.addEventListener("brewEnd", coffeeReadyEffect);
window.addEventListener("tick", updateTime);
window.addEventListener("tick", updateBrewDiff);
window.addEventListener("dtUpdate", updateBrewTime);
window.addEventListener("dtUpdate", hotEffect);
//start time based events
setInterval(tick, 100);
tick();
});
$(window).resize(resize);
-41
View File
@@ -1,41 +0,0 @@
{% load i18n %}
{% load static %}
<html>
<head>
<title>Coffee Cups @Guild Room - AYY SIK ry</title>
<meta charset="UTF-8">
<meta http-equiv="refresh" content="3600">
<meta http-equiv="cache-control" content="max-age=0" />
<meta http-equiv="cache-control" content="no-cache" />
<meta http-equiv="expires" content="0" />
<meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
<meta http-equiv="pragma" content="no-cache" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.2/mqttws31.js"
type="text/javascript"></script>
<link rel="stylesheet" href="{% static "coffee_scale/css/coffee.css" %}">
<script src="{% static "coffee_scale/js/coffee.js" %}"></script>
</head>
<body>
<div id="container">
<span id="brewtime" class="brewtime layerone"></span>
<span class="brewtime layertwo">:)</span>
<span id="address">
ka.dy.fi
<noscript><br>This page uses JavaScript!</noscript>
<br><span id="time"></span></span>
</span>
<div id="upper">
</div>
<!--Kahvinkeitin on rikki. Varakeittimellä keitettyä kahvia saattaa olla.-->
<div id="lower" class="normal">
<div id="text" class="text layerone">???</div>
<div class="text layertwo">&nbsp;+</div>
<div id="scale"><div id="scale2"></div></div>
</div>
</div>
</body>
</html>
-2
View File
@@ -1,2 +0,0 @@
from django.test import TestCase, Client
from django.conf import settings
-12
View File
@@ -1,12 +0,0 @@
from django.conf.urls import url
from django.conf import settings
from .views import coffee_view
urlpatterns = [
# landing page
url(r'^$', coffee_view),
]
if settings.DEBUG:
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
urlpatterns += staticfiles_urlpatterns()
-11
View File
@@ -1,11 +0,0 @@
from django.shortcuts import render
from django.http import JsonResponse
from django.utils import timezone
import logging
from django.conf import settings
def coffee_view(request):
return render(request, 'coffee_scale:coffee.html')
@@ -1,30 +0,0 @@
from django.core.management.base import BaseCommand, CommandError
from django.contrib.auth.models import User, Permission
from rest_framework.authtoken.models import Token
class Command(BaseCommand):
user_name = "sahkopiikki"
password = User.objects.make_random_password()
def handle(self, *args, **options):
if User.objects.filter(username=self.user_name).exists():
self.stdout.write("Sahkopiikki user already exists. Skipping.")
user = User.objects.get(username=self.user_name)
token = Token.objects.get(user=user)
self.stdout.write("Token: {}".format(token))
return
u = User(username=self.user_name)
u.set_password(self.password)
u.save()
permission = Permission.objects.get(codename='check_by_email')
u.user_permissions.add(permission)
token = Token.objects.create(user=u)
self.stdout.write("Created sahkopiikki user '{}' with password '{}' and token '{}'.".format(
self.user_name, self.password, token
))
-35
View File
@@ -3,7 +3,6 @@
from django.test import TestCase, Client
from unittest import skip
from django.contrib.auth.models import User
from members.management.commands.createsahkopiikkiuser import Command as SahkopiikkiCommand
from members.models import Member, Payment, Request
from rest_framework.authtoken.models import Token
@@ -31,9 +30,6 @@ class MemberRegisterTestCase(TestCase):
self.c = Client()
self.c.login(username=username, password=password)
sc = SahkopiikkiCommand()
sc.handle()
def test_member_created(self):
"""Test member creation."""
exists = Member.objects.filter(first_name="Tidus").exists()
@@ -78,37 +74,6 @@ class MemberRegisterTestCase(TestCase):
results = response.json()['results']
self.assertEqual(len(results), 0)
def test_sahkopiikki_check_by_email_not_found(self):
"""Test if sähköpiikki auth and search work"""
email = 'teppo@tulppu.fi'
wrong_email = 'asd@asd.fi'
Member.objects.create(email=email, first_name='Teppo', last_name='Tulppu')
token = Token.objects.get(user__username='sahkopiikki').key
self.c.defaults['HTTP_AUTHORIZATION'] = 'Token ' + token
response = self.c.get('/members/check?email={}'.format(wrong_email), follow=True)
self.assertEqual(response.json()['exists'], False)
def test_sahkopiikki_check_by_email_found(self):
"""Test if sähköpiikki auth and search work"""
email = 'teppo@tulppu.fi'
Member.objects.create(email=email, first_name='Teppo', last_name='Tulppu')
token = Token.objects.get(user__username='sahkopiikki').key
self.c.defaults['HTTP_AUTHORIZATION'] = 'Token ' + token
response = self.c.get('/members/check?email={}'.format(email), follow=True)
self.assertEqual(response.json()['exists'], True)
def test_sahkopiikki_check_by_email_forbidden(self):
"""Test if sähköpiikki auth and search work"""
email = 'teppo@tulppu.fi'
Member.objects.create(email=email, first_name='Teppo', last_name='Tulppu')
token = Token.objects.get(user__username='sahkopiikki').key
self.c.defaults['HTTP_AUTHORIZATION'] = 'Token ' + token + 'DERP'
response = self.c.get('/members/check?email={}'.format(email), follow=True)
self.assertEqual(response.status_code, 401)
def test_export_members_excel(self):
"""Test if the user can download an excel file of the member register"""
resp = self.c.get('/members/export_members')
+3953 -1647
View File
File diff suppressed because it is too large Load Diff
+9 -13
View File
@@ -6,8 +6,9 @@
"lint": "run-p lint:js lint:md lint:py",
"lint:js": "eslint .",
"lint:md": "remark .",
"lint:py": "pycodestyle --config=setup.cfg --count .",
"lint:py-type": "pyright"
"lint:py": "pycodestyle --config=pycodestyle.cfg --count .",
"lint:py-type": "pyright",
"prepare": "husky install"
},
"repository": {
"type": "git",
@@ -16,21 +17,16 @@
"author": "SIK ry",
"license": "ISC",
"dependencies": {
"eslint": "3.19.0",
"husky": "4.2.5",
"npm-run-all": "4.1.5",
"pyright": "1.1.42",
"remark-cli": "7.0.0",
"remark-preset-lint-recommended": "3.0.3"
"eslint": "^7.23.0",
"husky": "^5.2.0",
"npm-run-all": "^4.1.5",
"pyright": "^1.1.126",
"remark-cli": "^9.0.0",
"remark-preset-lint-recommended": "^5.0.0"
},
"remarkConfig": {
"plugins": [
"remark-preset-lint-recommended"
]
},
"husky": {
"hooks": {
"pre-push": "npm run lint"
}
}
}
Generated
+1242
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -1,4 +1,4 @@
[pycodestyle]
max-line-length = 120
ignore = E501,E722
exclude = '*/migrations/*', venv/*
exclude = '*/migrations/*', venv/*, .venv/*
+60
View File
@@ -0,0 +1,60 @@
[tool.poetry]
name = "web2.0-backend"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]
[tool.poetry.dependencies]
python = "^3.9"
Pillow = "^8.1.2"
"backports.shutil_get_terminal_size" = "1.0.0"
decorator = "4.0.9"
Django = "2.1.5"
ipython = "4.2.0"
ipython_genutils = "0.1.0"
pexpect = "4.1.0"
pickleshare = "0.7.2"
ptyprocess = "0.5.1"
pytz = "2016.4"
simplegeneric = "0.8.1"
traitlets = "4.2.1"
requests = "2.11.1"
django-nocaptcha-recaptcha = "0.0.19"
django-cors-headers = "2.0.1"
djangorestframework = "3.8.2"
PyJWT = "1.6.4"
djangorestframework-jwt = "1.11.0"
coverage = "4.3.4"
django-nose = "1.4.5"
nose-exclude = "0.5.0"
psycopg2-binary = "2.8.4"
django-bootstrap3 = "11.1.0"
django-tables2 = "1.6.1"
pycodestyle = "2.6.0"
dealer = "2.0.5"
django-modeltranslation = "0.13b1"
django-auditlog = "0.4.5"
phonenumbers = "8.11.4"
django-phonenumber-field = {version = "4.0.0", extras = ["phonenumbers"]}
django-autocomplete-light = "3.4.1"
six = "1.12.0"
django-suit = "0.2.26"
telepot = "12.3"
pyexcel = "0.5.14"
pyexcel-xlsx = "0.5.8"
django-import-export = "0.7.0"
openpyxl = "2.6.4"
django-app-namespace-template-loader = "0.4.1"
django-filter = "2.0.0"
whitenoise = "4.1.4"
jsonschema = "3.2.0"
mailjet-rest = "1.3.3"
Markdown = "3.2.2"
uWSGI = "2.0.18"
gunicorn = "19.9.0"
[tool.poetry.dev-dependencies]
[build-system]
requires = ["poetry-core>=1.0.0"]
build-backend = "poetry.core.masonry.api"
+1 -5
View File
@@ -16,10 +16,6 @@ Data table app for viewing and modifying the member register, member application
Mostly static website with an event calendar and news feed.
### Coffee scale
Shows the current coffee scale status.
## Accessing the source
### Clone this repository and enter it
@@ -98,7 +94,7 @@ Merge requests to `master` should be reviewed by multiple developers. Only a mod
Lint python files using `pycodestyle` with
```bash
pycodestyle --config=setup.cfg --count .
pycodestyle --config=pycodestyle.cfg --count .
```
Lint javascript and markdown using `eslint` and `remark` with
-40
View File
@@ -1,40 +0,0 @@
# Certs
# Modify CNs as needed
$cert = New-SelfSignedCertificate -Type Custom -KeySpec Signature `
-Subject "CN=AarniP2SRootCert" -KeyExportPolicy Exportable `
-HashAlgorithm sha256 -KeyLength 2048 `
-CertStoreLocation "Cert:\CurrentUser\My" -KeyUsageProperty Sign -KeyUsage CertSign
New-SelfSignedCertificate -Type Custom -DnsName P2SChildCert -KeySpec Signature `
-Subject "CN=AarniP2SChildCert" -KeyExportPolicy Exportable `
-HashAlgorithm sha256 -KeyLength 2048 `
-CertStoreLocation "Cert:\CurrentUser\My" `
-Signer $cert -TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2")
# Export RootCert (cert manager)
https://docs.microsoft.com/fi-fi/azure/vpn-gateway/vpn-gateway-certificates-point-to-site#install
# Install RootCert to Azure and _SAVE_
- sikdata-vpn/User VPN configuration
# REMEMBER TO PRESS SAVE IN AZURE BEFORE NEXT STEP
# Download Client setup
- Download VPN client button
# Run correct exe (x64 vs x86)
# Windows settings VPN, connect to sikdata-vnet (should be installed by the executable)
# PROFIT!!!
$connectTestResult = Test-NetConnection -ComputerName 192.168.0.4 -Port 445
if ($connectTestResult.TcpTestSucceeded) {
# Save the password so the drive will persist on reboot
cmd.exe /C "cmdkey /add:`"192.168.0.4`" /user:`"Azure\sikdata`" /pass:`"<password>`""
# Mount the drive
New-PSDrive -Name Z -PSProvider FileSystem -Root "\\192.168.0.4\sikdata" -Persist
} else {
Write-Error -Message "Unable to reach the Azure storage account via port 445. Check to make sure your organization or ISP is not blocking port 445, or use Azure P2S VPN, Azure S2S VPN, or Express Route to tunnel SMB traffic over a different port."
}
+1 -2
View File
@@ -86,7 +86,6 @@ INSTALLED_APPS = [
'webapp',
'members',
'infoscreen',
'coffee_scale',
'kaehmy',
'ohlhafv',
'rest_framework',
@@ -197,7 +196,7 @@ REST_FRAMEWORK = {
'sustained': '1000/day'
},
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.LimitOffsetPagination',
'PAGE_SIZE': 10,
'PAGE_SIZE': 1000,
'DEFAULT_FILTER_BACKENDS': (
'django_filters.rest_framework.DjangoFilterBackend',
),
-2
View File
@@ -26,7 +26,6 @@ from django.views.generic.base import RedirectView
import webapp.urls
import infoscreen.urls
import members.urls
import coffee_scale.urls
favicon_view = RedirectView.as_view(
url='static/img/favicon.png', permanent=True)
@@ -36,7 +35,6 @@ urlpatterns = [
url(r'', include('webapp.urls')),
url(r'^members/', include('members.urls')),
url(r'^infoscreen/', include('infoscreen.urls')),
url(r'^coffee/', include('coffee_scale.urls')),
url(r'^kaehmy/', include('kaehmy.urls')),
url(r'^ohlhafv/', include('ohlhafv.urls')),
+6 -6
View File
@@ -43,9 +43,9 @@ class EventViewSet(ModelViewSet):
ordering = ["start_time"]
serializer_class = EventSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
# filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
# filter_fields = '__all__'
# search_fields = '__all__'
filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
filter_fields = ('id', 'tags', 'visible', 'signupForm')
search_fields = ('id', 'tags', 'visible', 'signupForm')
def get_queryset(self):
@@ -224,9 +224,9 @@ class FeedViewSet(ModelViewSet):
queryset = Feed.objects.filter(deleted=False)
serializer_class = FeedSerializer
permission_classes = [IsAuthenticatedOrReadOnly]
# filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
# filter_fields = '__all__'
# search_fields = '__all__'
filter_backends = (filters.DjangoFilterBackend, SearchFilter, OrderingFilter)
filter_fields = ('id', 'tags', 'visible')
search_fields = ('id', 'tags', 'visible')
def get_queryset(self):
if self.request.user.is_authenticated: