Commit 7255c33d authored by Florent Chehab's avatar Florent Chehab

Render Generic Module partly connected

parent aa616a2a
......@@ -8,6 +8,8 @@
from django.conf.urls import url, include
from django.conf import settings
from rest_framework import routers
from django.urls import path
from . import views
ALL_MODELS = []
ALL_VIEWSETS = []
......@@ -51,6 +53,7 @@ router.register(
){% endif %}{% endfor %}
urlpatterns += [url(r'^api/', include(router.urls))]
urlpatterns.append(path('api/serverModerationStatus/', views.app_moderation_status))
for model in ALL_MODELS:
for key in model.model_config:
......
......@@ -4,13 +4,14 @@ from backend.models.university import University
from backend.fields import JSONField
from backend.models.abstract.my_model import MyModel, MyModelSerializer, MyModelViewSet
from django.contrib.auth.models import User
from backend.utils import get_viewset_permissions, get_model_config
from backend.utils import get_viewset_permissions, get_model_config, get_user_level
class UserData(MyModel):
model_config = get_model_config("UserData")
owner = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True)
owner = models.OneToOneField(
User, on_delete=models.CASCADE, primary_key=True)
contact_info = JSONField(default=dict)
contact_info_is_public = models.BooleanField(default=False)
config = JSONField(default=dict)
......@@ -21,6 +22,10 @@ class UserData(MyModel):
class UserDataSerializer(MyModelSerializer):
owner = serializers.CharField(read_only=True)
owner_level = serializers.SerializerMethodField()
def get_owner_level(self, obj):
return get_user_level(obj.owner)
def my_pre_save(self):
self.override_validated_data({'owner': self.user})
......
......@@ -3,6 +3,15 @@ from django.conf import settings
from .obj_moderation_permission import OBJ_MODERATION_PERMISSIONS
######################################
######################################
##
# IF YOU TOUCH THIS FILE, MODIFY IT'S
# JS EQUIVALENT (isModerationRequired)
##
######################################
######################################
def is_moderation_required(model_moderation_level, obj_in_db, user, user_level=None):
if user_level is None:
user_level = get_user_level(user)
......
......@@ -8,7 +8,8 @@ def get_model_config(model):
if obj['model'] == model:
return {
"moderation_level": obj["moderation_level"],
"model": model
"model": model,
"read_only": obj["read_only"]
}
raise Exception("Model not found in API configuraiton, cannot process !")
from django.conf import settings
from django.http import HttpResponse
import json
from backend.permissions.obj_moderation_permission import OBJ_MODERATION_PERMISSIONS
def app_moderation_status(request):
return HttpResponse(json.dumps({
'activated': settings.MODERATION_ACTIVATED,
'moderator_level': OBJ_MODERATION_PERMISSIONS["moderator"]
}))
......@@ -4,7 +4,7 @@ from os import makedirs
from os.path import join, dirname, realpath, exists
from django import template
import re
import yaml
from general.api import get_api_config
############
# Need to do this first so that Django template engine is working
......@@ -37,10 +37,8 @@ templates = [
'combinedReducers'
]
api_config = get_api_config()
API_BASE = "/api/"
with open(join(current_dir, '../../general/api/api_config.yml'), 'r') as f:
data = f.read()
api_config = yaml.load(data)
contexts = []
for api in api_config:
......@@ -53,6 +51,12 @@ for api in api_config:
"api_end_point": API_BASE + api["api_end_point"] + '/',
})
# API outside rest framework here
contexts.append({
"name": 'serverModerationStatus',
"api_end_point": API_BASE + 'serverModerationStatus' + '/',
})
def convert(name):
s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
......
import React, { Component } from 'react';
import Loading from './other/Loading';
import _ from 'underscore';
class MyComponent extends Component {
customErrorHandlers = {}
idToUse = null;
......@@ -14,9 +14,6 @@ class MyComponent extends Component {
const { props } = this;
for (let prop_key in props) {
let prop = props[prop_key];
// if (typeof prop == 'boolean') {
// continue;
// }
if (prop === Object(prop) && 'fetched' in prop) {
out[prop_key] = prop.fetched.data;
}
......@@ -37,9 +34,6 @@ class MyComponent extends Component {
const { props } = this;
for (let el in props) {
let prop = props[el];
// if (typeof prop == 'boolean') {
// continue;
// }
if (prop === Object(prop) && val in prop && prop[val]) {
return true;
}
......@@ -51,9 +45,6 @@ class MyComponent extends Component {
const { props } = this;
for (let prop_key in props) {
let prop = props[prop_key];
if (typeof prop == 'boolean') {
continue;
}
if (prop === Object(prop) && 'fetchHasError' in prop) {
if (prop.fetchHasError.status) {
if (prop_key in this.customErrorHandlers) {
......@@ -80,36 +71,35 @@ class MyComponent extends Component {
return this.checkProps('isSaving');
}
loadPropsIfNeeded() {
loadPropsIfNeeded(dontFetch = false) {
let propsLoaded = true;
if (this.checkPropsHasError()) {
return false;
}
const { props } = this;
for (let prop_key in props) {
let prop = props[prop_key];
if (typeof prop == 'boolean') {
continue;
}
if (prop === Object(prop) && 'fetched' in prop) {
if ((!prop.fetched.fetchedAt) || prop.invalidated) {
if (!prop.isLoading) {
if (this.idToUse) {
props.fetchData[prop_key](this.props[this.idToUse]);
} else {
props.fetchData[prop_key]();
propsLoaded = false;
if (!dontFetch) {
if (!prop.isLoading) {
if (this.idToUse) {
props.fetchData[prop_key](this.props[this.idToUse]);
} else {
props.fetchData[prop_key]();
}
}
}
}
}
}
return propsLoaded;
}
allFetchedDataReady(){
if (this.checkPropsHasError() ||this.checkPropsIsLoading() || this.checkPropsInvalidated() || this.checkPropsIsSaving()) {
return false;
} else {
return true;
}
allFetchedDataAreReady() {
return this.loadPropsIfNeeded(true);
}
componentDidMount() {
......@@ -130,7 +120,7 @@ class MyComponent extends Component {
return <p>Sorry! There was an error loading the items</p>;
}
if (!this.allFetchedDataReady()) {
if (!this.allFetchedDataAreReady()) {
return <Loading />;
}
......
......@@ -20,7 +20,7 @@ class ThemeProvider extends MyComponent {
state = { theme: this.props.themeSavedInTheApp.theme };
myComponentDidUpdate() {
if (!this.allFetchedDataReady()) {
if (!this.allFetchedDataAreReady()) {
return;
}
......
......@@ -89,7 +89,7 @@ class ColorTool extends MyComponent {
}
myComponentDidUpdate() {
if (!this.allFetchedDataReady()) {
if (!this.allFetchedDataAreReady()) {
return
}
......
......@@ -88,11 +88,11 @@ class UniversityTemplate extends React.Component {
</Tabs>
</AppBar>
{value === 0 && <TabContainer> <GeneralInfoTab univId={univId} /> </TabContainer>}
{value === 1 && <TabContainer> <UniversityMoreTab univId={univId} /> </TabContainer>}
{value === 2 && <TabContainer> <PreviousDeparturesTab univId={univId} /> </TabContainer>}
{value === 3 && <TabContainer> <ScholarshipsTab univId={univId} /> </TabContainer>}
{value === 4 && <TabContainer> <CampusesCitiesTab univId={univId} /> </TabContainer>}
{value === 5 && <TabContainer> <MoreTab univId={univId} /> </TabContainer>}
{/* {value === 1 && <TabContainer> <UniversityMoreTab univId={univId} /> </TabContainer>} */}
{/* {value === 2 && <TabContainer> <PreviousDeparturesTab univId={univId} /> </TabContainer>} */}
{/* {value === 3 && <TabContainer> <ScholarshipsTab univId={univId} /> </TabContainer>} */}
{/* {value === 4 && <TabContainer> <CampusesCitiesTab univId={univId} /> </TabContainer>} */}
{/* {value === 5 && <TabContainer> <MoreTab univId={univId} /> </TabContainer>} */}
</div>
</div>
......
import React from 'react';
import { withStyles } from '@material-ui/core/styles';
import Tooltip from '@material-ui/core/Tooltip';
import Button from '@material-ui/core/Button';
import IconButton from '@material-ui/core/IconButton';
import classNames from 'classnames';
import PropTypes from 'prop-types';
import { withStyles } from '@material-ui/core/styles';
import { compose } from 'recompose';
import { connect } from "react-redux";
import Grid from '@material-ui/core/Grid';
import Typography from '@material-ui/core/Typography';
import Paper from '@material-ui/core/Paper';
import Chip from '@material-ui/core/Chip';
import Avatar from '@material-ui/core/Avatar';
import Divider from '@material-ui/core/Divider';
import SettingsBackRestoreIcon from '@material-ui/icons/SettingsBackupRestore';
import CreateIcon from '@material-ui/icons/Create';
import VerifiedUserIcon from '@material-ui/icons/VerifiedUser';
import LinkIcon from '@material-ui/icons/Link';
import NotificationImportantIcon from '@material-ui/icons/NotificationImportant';
import FullScreenDialog from '../../shared/FullScreenDialog';
import MyBadge from '../../shared/MyBadge';
import red from '@material-ui/core/colors/red';
import orange from '@material-ui/core/colors/orange';
import green from '@material-ui/core/colors/green';
import MyComponent from '../../MyComponent';
import {
serverModerationStatusFetchData,
userDataElFetchData
} from '../../../generated/actions';
import renderUsefulLinks from './genericModuleFunctions/renderUsefulLinks';
import renderFirstRow from './genericModuleFunctions/renderFirstRow';
const styles = theme => ({
root: {
flexGrow: 1,
......@@ -63,7 +59,7 @@ const styles = theme => ({
chip: {
margin: theme.spacing.unit,
},
titleContainer:{
titleContainer: {
display: "flex",
alignItems: "center",
},
......@@ -73,143 +69,9 @@ const styles = theme => ({
}
})
function renderTitle() {
const { title, classes, importanceLevel } = this.props;
if (title) {
if (importanceLevel) {
let c = classNames(classes.titleIcon);
if (importanceLevel == "IMPORTANT"){
c = classNames(classes.titleIcon, classes.red)
} else if (importanceLevel == "important"){
c = classNames(classes.titleIcon, classes.orange)
}
return (
<div className={classes.titleContainer}>
<NotificationImportantIcon className={c} />
<Typography variant='display1'>{title}</Typography>
</div>
)
} else {
return (
<Typography variant='display1'>{title}</Typography>
)
}
} else {
return (<div></div>)
}
}
function renderUpdateInfo() {
const { automaticData } = this.props;
if (automaticData) {
return (
<Typography variant='caption'>Dernière mise à jour le 08/09/2018 à 15h20</Typography>
)
} else {
return (
<Typography variant='caption'>Mis à jour par <em>chehabfl</em> le 08/09/2018 à 15h20</Typography>
)
}
}
function renderFirstRow() {
const { classes, title, automaticData, theme } = this.props;
let classEdit = "green",
classModer = "orange",
classVersion = "red";
if (automaticData) {
classEdit = "disabled";
classModer = "disabled";
classVersion = "disabled";
}
return (
<Grid container spacing={8}>
<Grid item xs style={{paddingBottom: theme.spacing.unit}}>
{renderTitle.bind(this)()}
{renderUpdateInfo.bind(this)()}
</Grid>
<Grid item xs={4} style={{ textAlign: 'right' }}>
<Tooltip title="Informations sur la modération" placement="top">
<div style={{ display: 'inline-block' }}> {/* Needed to fire events for the tooltip when below is disabled! when below is disabled!! */}
<MyBadge classes={{ badge: classes.badge }} badgeContent={null} color="secondary">
<IconButton aria-label="Modération" disabled={automaticData} className={classes.button}>
<VerifiedUserIcon className={classes[classModer]} />
</IconButton>
</MyBadge>
</div>
</Tooltip>
<Tooltip title="Informations sur les possibilités d'éditions" placement="top">
<div style={{ display: 'inline-block' }}> {/* Needed to fire events for the tooltip when below is disabled!! */}
<IconButton aria-label="Éditer" className={classes.button} disabled={automaticData} onClick={this.handleClickOpenFullScreenDialog}>
<CreateIcon className={classes[classEdit]} />
</IconButton>
</div>
</Tooltip>
<Tooltip title="Informations sur les versions disponibles" placement="top">
<div style={{ display: 'inline-block' }}> {/* Needed to fire events for the tooltip when below is disabled!! */}
<MyBadge classes={{ badge: classes.badge }} badgeContent={40} color="secondary">
<IconButton aria-label="Restorer" disabled={automaticData} className={classes.button}>
<SettingsBackRestoreIcon className={classes[classVersion]} />
</IconButton>
</MyBadge>
</div>
</Tooltip>
</Grid>
</Grid>
)
}
function renderUsefulLinks() {
const { classes, usefulLinks, theme} = this.props;
const nbItems = usefulLinks.length;
if (nbItems == 0) {
return (<div></div>)
}
const s = nbItems > 1 ? "s" : "";
return (
<div>
<Typography variant='caption' style={{paddingTop:2*theme.spacing.unit}}> Source{s} :</Typography>
<div className={classes.rootLinks}>
{
usefulLinks.map((el, index) => {
return (
<Chip
key={index}
avatar={
<Avatar>
<LinkIcon />
</Avatar>
}
label={el.description}
className={classes.chip}
color="secondary"
onClick={() => open(el.url, '_blank')}
variant="outlined"
/>
)
})
}
</div>
</div>
)
}
class GenericModule extends React.Component {
class GenericModule extends MyComponent {
state = {
fullScreenDialogOpen: false,
};
......@@ -222,14 +84,15 @@ class GenericModule extends React.Component {
this.setState({ fullScreenDialogOpen: false });
};
render() {
const { classes, title } = this.props;
myRender() {
console.log(renderFirstRow)
const { classes } = this.props;
return (
<div>
<FullScreenDialog open={this.state.fullScreenDialogOpen} handleClose={this.handleCloseFullScreenDialog} />
<Paper className={classes.root} square={true}>
{renderFirstRow.bind(this)()}
<div>
{this.props.children}
</div>
......@@ -243,9 +106,34 @@ class GenericModule extends React.Component {
GenericModule.defaultProps = {
title: null,
importanceLevel: null,
automaticData: false,
usefulLinks: []
usefulLinks: [],
};
GenericModule.propTypes = {
classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
modelData: PropTypes.object.isRequired
};
export default withStyles(styles, { withTheme: true })(GenericModule);
\ No newline at end of file
const mapStateToProps = (state) => {
return {
serverModerationStatus: state.app.serverModerationStatus,
userDataEl: state.userDataEl
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: {
userDataEl: () => dispatch(userDataElFetchData("")),
serverModerationStatus: () => dispatch(serverModerationStatusFetchData()),
}
};
};
export default compose(
withStyles(styles, { withTheme: true }),
connect(mapStateToProps, mapDispatchToProps)
)(GenericModule);
......@@ -12,22 +12,16 @@ import TableRow from '@material-ui/core/TableRow';
import Markdown from '../../shared/Markdown';
import Typography from '@material-ui/core/Typography';
import CloudQueueIcon from '@material-ui/icons/CloudQueue';
import LocalFloristIcon from '@material-ui/icons/LocalFlorist';
import TextLink from '../../other/TextLink';
import GenericModule from './GenericModule';
import Grid from '@material-ui/core/Grid';
import Divider from '@material-ui/core/Divider';
import MyComponent from '../../MyComponent';
import {
universitiesSemestersDatesElFetchData,
} from '../../../generated/actions';
const styles = theme => ({
......@@ -63,25 +57,19 @@ function convertDate(date) {
class UniversitySemestersDates extends MyComponent {
idToUse = "univId";
customErrorHandlers = {
universitiesSemestersDatesEl: (e) => {
console.log(e);
return true;
}
}
myRender() {
const { classes } = this.props;
console.log("props", this.props)
const semestersDates = this.getFetchedData('universitiesSemestersDatesEl');
let { autumn_begin, autumn_end, spring_begin, spring_end, comment } = semestersDates;
console.log("semestersDates", semestersDates);
autumn_begin = convertDate(autumn_begin);
autumn_end = convertDate(autumn_end);
spring_begin = convertDate(spring_begin);
spring_end = convertDate(spring_end);
return (
<GenericModule title={"Date des semestres"} usefulLinks={[{ description: "Site de l'EPFL", url: "https://epfl.ch" }]}>
<GenericModule title={"Date des semestres"} modelData={semestersDates}>
<div className={classes.root}>
<Table >
<TableHead>
......
import isModerationRequired from '../../../../utils/isModerationRequired';
export default function getEditTooltipAndClass(readOnly, modelData, userLevel, serverModerationStatus, moderatorLevel) {
if (readOnly) {
return {
editTooltip: "Ce contenu n'est pas concerné par l'édition.",
editClass: "disabled"
}
}
if (isModerationRequired(
serverModerationStatus,
moderatorLevel,
modelData.model_config.moderation_level,
modelData.obj_moderation_level,
userLevel
)) {
return {
editTooltip: "Vous pouvez éditer le contenu de ce module, mais votre contribution sera assujettie à la modération.",
editClass: "orange"
}
} else {
return {
editTooltip: "Vous pouvez éditer le contenu de ce module.",
editClass: "green"
}
}
}
\ No newline at end of file
export default function getModerationTooltipAndClass(nbPendingModeration, readOnly) {
if (readOnly) {
return {
moderTooltip: "Ce contenu n'est pas concerné par la modération.",
moderClass: "disabled"
}
}
if (nbPendingModeration == 0) {
return {
moderTooltip: "Aucune mise-à-jour de ce module est en attente de modération pour ce module.",
moderClass: "green"
}
} else if (nbPendingModeration == 1) {
return {
moderTooltip: "Une mise-à-jour de ce modèle est en attente de modération.",
moderClass: "orange"
}
} else {
return {
moderTooltip: "Plusieurs mises à jour de ce modèle sont en attente.",
moderClass: "orange"
}
}
}
\ No newline at end of file
export default function getVersionTooltipAndClass(nbVersions) {
if (typeof nbVersions == 'undefined') {
return {
versionTooltip: "Ce contenu n'est pas versionné.",
versionClass: "disabled"
}
}
if (nbVersions == 0) {
return {
versionTooltip: "Il n'existe pas d'autres versions de ce contenu.",
versionClass: "disabled"
}
} else if (nbVersions == 1) {
return {
versionTooltip: "Le contenu actuel constitue la première mise à jour de ce module, vous pouvez voir la version initiale en cliquant sur cette icône.",
versionClass: "green"
}
} else {
return {
versionTooltip: "Le contenu actuel constitue la " + nbVersions + "ème mise à jour de ce module, vous pouvez voir les versions précédentes en cliquant sur cette icône.",
versionClass: "green"
}
}
}
\ No newline at end of file
import React from 'react';
import renderTitle from './renderTitle';
import renderUpdateInfo from './renderUpdateInfo';
import getEditTooltipAndClass from './getEditTooltipAndClass';
import getVersionTooltipAndClass from './getVersionTooltipAndClass';
import getModerationTooltipAndClass from './getModerationTooltipAndClass';
import Grid from '@material-ui/core/Grid';
import MyBadge from '../../../shared/MyBadge';
import IconButton from '@material-ui/core/IconButton';
import VerifiedUserIcon from '@material-ui/icons/VerifiedUser';
import CreateIcon from '@material-ui/icons/Create';
import SettingsBackRestoreIcon from '@material-ui/icons/SettingsBackupRestore';
import Tooltip from '@material-ui/core/Tooltip';
export default function renderFirstRow() {
const { classes, theme } = this.props;
const { modelData } = this.props;
const nbVersions = modelData.nb_versions;
const nbPendingModeration = modelData.pending_moderation.length
const readOnly = modelData.model_config.read_only;
const { versionTooltip, versionClass } = getVersionTooltipAndClass(nbVersions);
const { moderTooltip, moderClass } = getModerationTooltipAndClass(nbPendingModeration, readOnly);
const userData = this.getFetchedData('userDataEl');
const serverModerationStatus = this.getFetchedData('serverModerationStatus');
const { editTooltip, editClass } = getEditTooltipAndClass(readOnly, modelData, userData.owner_level, serverModerationStatus.activated, serverModerationStatus.moderator_level);
console.log("render title", renderTitle)
return (
<Grid container spacing={8}>
<Grid item xs style={{ paddingBottom: theme.spacing.unit }}>