Commit 945e00a3 authored by Florent Chehab's avatar Florent Chehab
Browse files

University fully connected

Textfield ready
bug corrected
parent 8a0c601c
...@@ -27,7 +27,7 @@ class University(MyModel): ...@@ -27,7 +27,7 @@ class University(MyModel):
acronym = models.CharField(max_length=20, null=True, blank=True) acronym = models.CharField(max_length=20, null=True, blank=True)
logo = models.URLField(null=True, blank=True, validators=[ logo = models.URLField(null=True, blank=True, validators=[
validate_extension_django]) validate_extension_django])
website = models.URLField(null=True, blank=True) website = models.URLField(null=True, blank=True, max_length=300)
utc_id = models.IntegerField(unique=True) utc_id = models.IntegerField(unique=True)
......
...@@ -30,6 +30,17 @@ class MyComponent extends Component { ...@@ -30,6 +30,17 @@ class MyComponent extends Component {
return res; return res;
} }
findMainCampus(univId) {
const mainCampuses = this.getFetchedData('mainCampuses');
for (let main_campus_pk in mainCampuses) {
const campus = mainCampuses[main_campus_pk]
if (campus.university == univId) {
return campus;
}
}
return null;
}
checkProps(val) { checkProps(val) {
const { props } = this; const { props } = this;
for (let el in props) { for (let el in props) {
......
import React from 'react';
import PropTypes from 'prop-types';
import withStyles from '@material-ui/core/styles/withStyles';
import compose from 'recompose/compose';
import { connect } from "react-redux";
import Editor from '../shared/Editor';
import editorStyle from '../shared/editorStyle';
import TextField from '../shared/fields/TextField';
import {
universitiesElSaveData,
universitiesElSavingHasError
} from '../../../generated/actions';
const styles = theme => ({
...editorStyle(theme)
});
class UniversityGeneral extends Editor {
renderEditor() {
const { modelData } = this.props;
return (
<div>
<TextField label={"Nom de l'université"}
value={modelData.name}
required={true}
maxLength={200}
formManager={this}
fieldMapping={'name'}
/>
<TextField label={"Acronyme de l'université"}
value={modelData.acronym}
maxLength={20}
formManager={this}
fieldMapping={'acronym'}
/>
<TextField label={"Site internet de l'université"}
value={modelData.website}
maxLength={300}
isUrl={true}
formManager={this}
fieldMapping={'website'}
/>
<TextField label={"Logo de l'université"}
value={modelData.logo}
maxLength={300}
isUrl={true}
urlExtensions={['jpg', 'png', 'svg']}
formManager={this}
fieldMapping={'logo'}
/>
</div>
)
}
}
UniversityGeneral.propTypes = {
modelData: PropTypes.object.isRequired,
};
const mapStateToProps = (state) => {
let lastUpdateTime = null;
const tmp = state.universitiesEl.fetched;
if (tmp.fetchedAt) {
lastUpdateTime = tmp.data.updated_on;
}
return {
savingHasError: state.universitiesEl.savingHasError,
lastSave: state.universitiesEl.fetched.fetchedAt,
lastUpdateTime,
};
};
const mapDispatchToProps = (dispatch) => {
return {
saveData: (data) => dispatch(universitiesElSaveData(data)),
clearSaveError: () => dispatch(universitiesElSavingHasError(false))
};
};
export default compose(
withStyles(styles, { withTheme: true }),
connect(mapStateToProps, mapDispatchToProps)
)(UniversityGeneral);
\ No newline at end of file
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import withStyles from '@material-ui/core/styles/withStyles'; import withStyles from '@material-ui/core/styles/withStyles';
import compose from 'recompose/compose';
import { connect } from "react-redux";
import _ from 'underscore';
import Markdown from '../../shared/Markdown';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import PhotoIcon from '@material-ui/icons/Photo';
import MyComponent from '../../MyComponent'; import MyComponent from '../../MyComponent';
import TextLink from '../../other/TextLink'; import TextLink from '../../other/TextLink';
import GenericModule from '../shared/GenericModule';
import Grid from '@material-ui/core/Grid'; import Grid from '@material-ui/core/Grid';
import Divider from '@material-ui/core/Divider'; import Divider from '@material-ui/core/Divider';
import GenericModule from '../shared/GenericModule';
import UniversityGeneralEditor from '../editors/UniversityGeneralEditor';
const styles = theme => ({ import {
universitiesElFetchData,
citiesFetchData,
countriesFetchData,
mainCampusesFetchData,
} from '../../../generated/actions';
const styles = theme => ({
}); });
function renderCore(rawModelData, classes, outsideData) {
const univInfos = rawModelData;
const { name, acronym, logo, website, comment } = univInfos;
const { city, country } = outsideData;
return (
<div>
<Grid container spacing={16} direction='row'>
<Grid item xs={4}>
<img style={{ width: "100%" }} src={logo} />
</Grid>
<Grid item xs>
<Typography variant='headline'>{name}</Typography>
<Typography variant='title'>{acronym}</Typography>
<Divider />
<Typography variant='subheading'>{city}, {country}</Typography>
<Typography variant='body1'> Site internet : <TextLink href={website}>{website}</TextLink> </Typography>
</Grid>
</Grid>
<Markdown source={comment} />
</div>
)
}
class UniversityGeneral extends React.Component { function parseRawModelData(rawModelData) {
render() { // reverse serialization
const { classes, theme } = this.props; const univInfos = rawModelData;
return ( const modelData = _.pick(univInfos,
<GenericModule title={"Introduction"}> [
"name",
<Grid container spacing={16} direction='row'> "acronym",
<Grid item xs={4}> "logo",
<img style={{ width: "100%" }} src={"https://upload.wikimedia.org/wikipedia/commons/f/f4/Logo_EPFL.svg"} /> "website",
</Grid> // "useful_links",
<Grid item xs> // "comment",
<Typography variant='headline'> École Polytechnique Fédérale de Lausanne</Typography> "university",
<Typography variant='title'>EPFL</Typography> // "obj_moderation_level",
<Divider /> "id"
<Typography variant='subheading'>Lausanne, Suisse</Typography> ]);
<Typography variant='body1'> Site internet : <TextLink href={"https://www.epfl.ch"}> www.epfl.ch </TextLink> </Typography>
</Grid>
</Grid>
</GenericModule> return modelData;
}
class UniversityGeneral extends MyComponent {
idToUse = "univId";
myRender() {
const univInfos = this.getFetchedData('universitiesEl');
const { classes } = this.props;
const univMainCampus = this.findMainCampus(this.props.univId);
const { cities, countries } = this.getAllFetchedData();
const cityModel = cities[univMainCampus.city];
const countryModel = countries[cityModel.country];
const outsideData = {
city: cityModel.name,
country: countryModel.name
}
return (
<GenericModule
buildTitle={() => "Présentation"}
rawModelData={univInfos}
parseRawModelData={parseRawModelData}
editor={UniversityGeneralEditor}
renderCore={renderCore}
coreClasses={classes}
outsideData={outsideData}
/>
) )
} }
} }
export default withStyles(styles, { withTheme: true })(UniversityGeneral); UniversityGeneral.propTypes = {
classes: PropTypes.object.isRequired,
theme: PropTypes.object.isRequired,
univId: PropTypes.string.isRequired
};
const mapStateToProps = (state) => {
return {
universitiesEl: state.universitiesEl,
countries: state.countries,
cities: state.cities,
mainCampuses: state.mainCampuses
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: {
universitiesEl: (univId) => dispatch(universitiesElFetchData(univId)),
countries: () => dispatch(countriesFetchData()),
cities: () => dispatch(citiesFetchData()),
mainCampuses: () => dispatch(mainCampusesFetchData()),
},
};
};
export default compose(
withStyles(styles, { withTheme: true }),
connect(mapStateToProps, mapDispatchToProps)
)(UniversityGeneral);
\ No newline at end of file
...@@ -177,7 +177,7 @@ class GenericModule extends MyComponent { ...@@ -177,7 +177,7 @@ class GenericModule extends MyComponent {
renderCore = (rawModelData) => ( renderCore = (rawModelData) => (
<div> <div>
{this.props.renderCore(rawModelData, this.props.coreClasses)} {this.props.renderCore(rawModelData, this.props.coreClasses, this.props.outsideData)}
{renderUsefulLinks(rawModelData, this.props.classes, this.props.theme)} {renderUsefulLinks(rawModelData, this.props.classes, this.props.theme)}
</div> </div>
) )
...@@ -229,7 +229,7 @@ class GenericModule extends MyComponent { ...@@ -229,7 +229,7 @@ class GenericModule extends MyComponent {
/> />
<Paper className={classes.root} square={true}> <Paper className={classes.root} square={true}>
{renderFirstRow.bind(this)(userCanModerate)} {renderFirstRow.bind(this)(userCanModerate)}
{this.renderCore(this.props.rawModelData, this.props.coreClasses)} {this.renderCore(this.props.rawModelData)}
</Paper> </Paper>
</div> </div>
) )
...@@ -249,6 +249,7 @@ GenericModule.propTypes = { ...@@ -249,6 +249,7 @@ GenericModule.propTypes = {
renderCore: PropTypes.func.isRequired, renderCore: PropTypes.func.isRequired,
parseRawModelData: PropTypes.func.isRequired, parseRawModelData: PropTypes.func.isRequired,
coreClasses: PropTypes.object.isRequired, coreClasses: PropTypes.object.isRequired,
outsideData: PropTypes.object,
}; };
......
...@@ -2,28 +2,42 @@ ...@@ -2,28 +2,42 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import withStyles from '@material-ui/core/styles/withStyles'; import withStyles from '@material-ui/core/styles/withStyles';
import Grid from '@material-ui/core/Grid';
import compose from 'recompose/compose'; import compose from 'recompose/compose';
import FieldWrapper from './FieldWrapper'; import FieldWrapper from './FieldWrapper';
import { TextField as MuiTextField } from '@material-ui/core/TextField'; import { TextField as MuiTextField } from '@material-ui/core';
import Typography from '@material-ui/core/Typography'; import Typography from '@material-ui/core/Typography';
import Markdown from '../../../shared/Markdown';
import LinkText from '../../../other/TextLink';
import Field from './Field'; import Field from './Field';
import isUrl from '../../../../utils/isUrl';
import stringHasExtension from '../../../../utils/stringHasExtension';
const styles = theme => ({ const styles = theme => ({
}) })
class TextField extends Field { class TextField extends Field {
hasError(value) { hasError(value) {
let messages = Array(); let messages = Array();
if (this.props.required && value == '') { if (this.props.required && value == '') {
messages.push("Ce champ est requis"); messages.push("Ce champ est requis mais il est vide.");
} }
if (this.props.maxLength && value.length > this.props.maxLength) { if (this.props.maxLength && value.length > this.props.maxLength) {
messages.push("Il y a trop de caractères.") messages.push("L'URL est trop long.");
}
if (this.props.isUrl && !isUrl(value)) {
messages.push("L'URL entrer n'est pas reconnu.")
} }
return this.buildError(messages); if (this.props.isUrl && this.props.urlExtensions.length > 0) {
if (!stringHasExtension(value, this.props.urlExtensions)) {
messages.push("Extension de l'URL non conforme")
}
}
return this.buildError(messages)
} }
handleChangeValue = (val) => { handleChangeValue = (val) => {
...@@ -33,14 +47,12 @@ class TextField extends Field { ...@@ -33,14 +47,12 @@ class TextField extends Field {
if (maxLength && value.length > maxLength + 1) { if (maxLength && value.length > maxLength + 1) {
value = value.substring(0, maxLength + 1); value = value.substring(0, maxLength + 1);
} }
this.setState({ value }); this.setState({ value });
} }
render() { render() {
const { classes } = this.props; const { classes } = this.props;
return ( return (
<FieldWrapper <FieldWrapper
required={this.props.required} required={this.props.required}
...@@ -48,7 +60,20 @@ class TextField extends Field { ...@@ -48,7 +60,20 @@ class TextField extends Field {
errors={this.state.error.messages} errors={this.state.error.messages}
label={this.props.label} label={this.props.label}
> >
{
this.props.isUrl ?
<div>
<Typography variant='caption'>Un URL est attendu ici (Le protocole - http/https/ftp - est requis !)</Typography>
{
this.props.urlExtensions.length > 0 ?
<Typography variant='caption'>L'url doit terminer par l'une des extensions suivantes (majuscule ou miniscule) : {JSON.stringify(this.props.urlExtensions)}</Typography>
:
<div></div>
}
</div>
:
<div></div>
}
{ {
this.props.maxLength ? this.props.maxLength ?
<Typography variant='caption'>Nombre de caractères : {this.state.value.length}/{this.props.maxLength}</Typography> <Typography variant='caption'>Nombre de caractères : {this.state.value.length}/{this.props.maxLength}</Typography>
...@@ -58,10 +83,10 @@ class TextField extends Field { ...@@ -58,10 +83,10 @@ class TextField extends Field {
<MuiTextField <MuiTextField
placeholder={"Le champ est vide"} placeholder={"Le champ est vide"}
fullWidth={true} fullWidth={true}
multiline={false}
value={this.state.value} value={this.state.value}
onChange={(e) => this.handleChangeValue(e.target.value)} onChange={(e) => this.handleChangeValue(e.target.value)}
/> />
</FieldWrapper> </FieldWrapper>
) )
} }
...@@ -71,11 +96,15 @@ class TextField extends Field { ...@@ -71,11 +96,15 @@ class TextField extends Field {
TextField.defaultProps = { TextField.defaultProps = {
value: '', value: '',
maxLength: 0, maxLength: 0,
isUrl: false,
urlExtensions: [],
} }
TextField.propTypes = { TextField.propTypes = {
value: PropTypes.string, value: PropTypes.string,
maxLength: PropTypes.number, maxLength: PropTypes.number,
isUrl: PropTypes.bool.isRequired,
urlExtensions: PropTypes.arrayOf(PropTypes.string.isRequired)
}; };
......
export default function getVersionTooltipAndClass(nbVersions) { export default function getVersionTooltipAndClass(nbVersions) {
if (typeof nbVersions == 'undefined') { if (typeof nbVersions == 'undefined' || isNaN(nbVersions)) {
return { return {
versionTooltip: "Ce contenu n'est pas versionné.", versionTooltip: "Ce contenu n'est pas versionné.",
versionClass: "disabled" versionClass: "disabled"
......
...@@ -6,7 +6,11 @@ import LinkIcon from '@material-ui/icons/Link'; ...@@ -6,7 +6,11 @@ import LinkIcon from '@material-ui/icons/Link';
export default function renderUsefulLinks(rawModelData, classes, theme) { export default function renderUsefulLinks(rawModelData, classes, theme) {
const usefulLinks = rawModelData.useful_links; const usefulLinks = rawModelData.useful_links;
if (!usefulLinks) {
return (<div></div>)
}
const nbItems = usefulLinks.length; const nbItems = usefulLinks.length;
if (nbItems == 0) { if (nbItems == 0) {
return (<div></div>) return (<div></div>)
......
...@@ -39,7 +39,7 @@ class GeneralInfoTab extends MyComponent { ...@@ -39,7 +39,7 @@ class GeneralInfoTab extends MyComponent {
<div style={{ flexGrow: 8, paddingRight: 2 * theme.spacing.unit }}> <div style={{ flexGrow: 8, paddingRight: 2 * theme.spacing.unit }}>
<Grid container direction='column' > <Grid container direction='column' >
<Grid item xs style={{ paddingBottom: 2 * theme.spacing.unit }} > <Grid item xs style={{ paddingBottom: 2 * theme.spacing.unit }} >
{/* <UniversityGeneral univId={this.props.univId} /> */} <UniversityGeneral univId={this.props.univId} />
</Grid> </Grid>
<Grid item xs> <Grid item xs>
{/* <UniversityDri univId={this.props.univId} /> */} {/* <UniversityDri univId={this.props.univId} /> */}
...@@ -66,7 +66,7 @@ class GeneralInfoTab extends MyComponent { ...@@ -66,7 +66,7 @@ class GeneralInfoTab extends MyComponent {
<Grid container spacing={16} > <Grid container spacing={16} >
<Grid item xs={12}> <Grid item xs={12}>
{/* <UniversityGeneral univId={this.props.univId} /> */} <UniversityGeneral univId={this.props.univId} />
</Grid> </Grid>
<Grid item xs={12}> <Grid item xs={12}>
{/* <UniversityDri univId={this.props.univId} /> */} {/* <UniversityDri univId={this.props.univId} /> */}
......
export default function stringHasExtension(str, allowedExtensions) {
if (!str || typeof str != 'string') {
return false;
}
const strExtension = str.split('.').slice(-1)[0].toLowerCase();
return allowedExtensions.some(ext => {
if (ext.toLowerCase() == strExtension) {
return true;
} else {
return false;
}