diff --git a/.gitignore b/.gitignore
index a8fc021eb1bc081bb14b24c8fd04a7663c9d9fd3..bae0397240c67c04b5c4ca6c623faddb5d4b4bf0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -14,3 +14,4 @@ database.db-journal
.idea
npm-debug.log
.env
+.history
diff --git a/CHANGELOG.md b/CHANGELOG.md
index ef92e00a6db650a4da0c3906622976b6702ee539..74978af5a878c64692cdbb2dd380379e3e16efd1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,15 @@
###### _TBD_
+## v2.3.0
+
+###### _3 May 2020_
+
+- [feat] add is_destination_open parameter in partner model and add transaction in utils.py
+- [fix] only show Read More button when necessary
+- [refacto] delete campus an city models
+- [refacto] frontend modify API calls so that only the university model is used
+
## v2.2.0
###### _26 April 2020_
diff --git a/backend/backend_app/admin.py b/backend/backend_app/admin.py
index e39c0db3e0430188c6b7c808f296748998e397d2..5911fce0f7cf52eaaed97697ce3ddb1e9ec38d89 100644
--- a/backend/backend_app/admin.py
+++ b/backend/backend_app/admin.py
@@ -4,8 +4,6 @@ from reversion_compare.admin import CompareVersionAdmin
from backend_app.models.abstract.versionedEssentialModule import (
VersionedEssentialModule,
)
-from backend_app.models.campus import Campus
-from backend_app.models.city import City
from backend_app.models.country import Country
from backend_app.models.countryDri import CountryDri
from backend_app.models.countryScholarship import CountryScholarship
@@ -33,8 +31,6 @@ from base_app.models import SiteInformation
ALL_MODELS = [
SiteInformation,
- Campus,
- City,
Country,
CountryDri,
CountryScholarship,
diff --git a/backend/backend_app/migrations/0003_auto_20200503_1514.py b/backend/backend_app/migrations/0003_auto_20200503_1514.py
new file mode 100644
index 0000000000000000000000000000000000000000..facd92bb5b94ab0e49e5b54819900c245eceff94
--- /dev/null
+++ b/backend/backend_app/migrations/0003_auto_20200503_1514.py
@@ -0,0 +1,17 @@
+# Generated by Django 2.1.7 on 2020-05-03 13:14
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [("backend_app", "0002_auto_20200417_1430")]
+
+ operations = [
+ migrations.AlterUniqueTogether(name="campus", unique_together=set()),
+ migrations.RemoveField(model_name="campus", name="city"),
+ migrations.RemoveField(model_name="campus", name="university"),
+ migrations.RemoveField(model_name="city", name="country"),
+ migrations.DeleteModel(name="Campus"),
+ migrations.DeleteModel(name="City"),
+ ]
diff --git a/backend/backend_app/migrations/0003_partner_is_destination_open.py b/backend/backend_app/migrations/0003_partner_is_destination_open.py
new file mode 100644
index 0000000000000000000000000000000000000000..7fa86251a356aae2b1662e87be75308b113648d9
--- /dev/null
+++ b/backend/backend_app/migrations/0003_partner_is_destination_open.py
@@ -0,0 +1,16 @@
+# Generated by Django 2.1.7 on 2020-05-03 09:30
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [("backend_app", "0002_auto_20200417_1430")]
+
+ operations = [
+ migrations.AddField(
+ model_name="partner",
+ name="is_destination_open",
+ field=models.BooleanField(default=False),
+ )
+ ]
diff --git a/backend/backend_app/migrations/0004_merge_20200503_1749.py b/backend/backend_app/migrations/0004_merge_20200503_1749.py
new file mode 100644
index 0000000000000000000000000000000000000000..63b984a53bd586b319b36cb8c3c111068a73c847
--- /dev/null
+++ b/backend/backend_app/migrations/0004_merge_20200503_1749.py
@@ -0,0 +1,13 @@
+# Generated by Django 2.1.7 on 2020-05-03 15:49
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("backend_app", "0003_partner_is_destination_open"),
+ ("backend_app", "0003_auto_20200503_1514"),
+ ]
+
+ operations = []
diff --git a/backend/backend_app/models/campus.py b/backend/backend_app/models/campus.py
deleted file mode 100644
index 95d940854d5c3625ffc5512452540ff4cdc6a680..0000000000000000000000000000000000000000
--- a/backend/backend_app/models/campus.py
+++ /dev/null
@@ -1,74 +0,0 @@
-from django.core.validators import MinValueValidator, MaxValueValidator
-from django.db import models
-
-from backend_app.models.abstract.base import (
- BaseModel,
- BaseModelSerializer,
- BaseModelViewSet,
-)
-from backend_app.models.city import City
-from backend_app.models.university import University
-from backend_app.permissions.app_permissions import ReadOnly
-
-
-class Campus(BaseModel):
- is_main_campus = models.BooleanField(null=False)
- name = models.CharField(max_length=200, default="", blank=True)
- city = models.ForeignKey(City, on_delete=models.PROTECT, null=False)
- university = models.ForeignKey(
- University,
- on_delete=models.PROTECT,
- null=False,
- related_name="university_campuses",
- )
-
- lat = models.DecimalField(
- max_digits=10,
- decimal_places=6,
- validators=[MinValueValidator(-85.05112878), MaxValueValidator(85.05112878)],
- )
-
- lon = models.DecimalField(
- max_digits=10,
- decimal_places=6,
- validators=[MinValueValidator(-180), MaxValueValidator(180)],
- )
-
- def location(self):
- return {"lat": self.lat, "lon": self.lon}
-
- class Meta:
- unique_together = ("is_main_campus", "university")
-
- def save(self, *args, **kwargs):
- raise Exception("Can't edit model anymore")
-
- def delete(self, *args, **kwargs):
- raise Exception("Can't edit model anymore")
-
-
-class CampusSerializer(BaseModelSerializer):
- class Meta:
- model = Campus
- fields = BaseModelSerializer.Meta.fields + (
- "is_main_campus",
- "name",
- "city",
- "university",
- "lat",
- "lon",
- )
-
-
-class CampusViewSet(BaseModelViewSet):
- permission_classes = (ReadOnly,)
- queryset = Campus.objects.all() # pylint: disable=E1101
- serializer_class = CampusSerializer
- end_point_route = "campuses"
-
-
-class MainCampusViewSet(BaseModelViewSet):
- queryset = Campus.objects.filter(is_main_campus=True)
- serializer_class = CampusSerializer
- permission_classes = (ReadOnly,)
- end_point_route = "mainCampuses"
diff --git a/backend/backend_app/models/city.py b/backend/backend_app/models/city.py
deleted file mode 100644
index b22b6471d475a6b23c9f2f9012abaaef75ef15d6..0000000000000000000000000000000000000000
--- a/backend/backend_app/models/city.py
+++ /dev/null
@@ -1,42 +0,0 @@
-from django.db import models
-
-from backend_app.models.abstract.base import (
- BaseModel,
- BaseModelSerializer,
- BaseModelViewSet,
-)
-from backend_app.models.country import Country
-from backend_app.permissions.app_permissions import ReadOnly
-
-
-class City(BaseModel):
- name = models.CharField(max_length=200)
- local_name = models.CharField(max_length=200, default="", blank=True)
- # We add an area to distinguish similarly named cities
- # in a country
- area = models.CharField(max_length=200, default="", blank=True)
- country = models.ForeignKey(Country, on_delete=models.PROTECT)
-
- def save(self, *args, **kwargs):
- raise Exception("Can't edit model anymore")
-
- def delete(self, *args, **kwargs):
- raise Exception("Can't edit model anymore")
-
-
-class CitySerializer(BaseModelSerializer):
- class Meta:
- model = City
- fields = BaseModelSerializer.Meta.fields + (
- "name",
- "local_name",
- "area",
- "country",
- )
-
-
-class CityViewSet(BaseModelViewSet):
- queryset = City.objects.all() # pylint: disable=E1101
- serializer_class = CitySerializer
- permission_classes = (ReadOnly,)
- end_point_route = "cities"
diff --git a/backend/backend_app/models/exchangeFeedback.py b/backend/backend_app/models/exchangeFeedback.py
index 172a3cb25296a0a55f213cec5dbd1fd2a2d90e7e..31c1f19a576e0a4578ff2e45c29b920d33374685 100644
--- a/backend/backend_app/models/exchangeFeedback.py
+++ b/backend/backend_app/models/exchangeFeedback.py
@@ -101,7 +101,7 @@ class ExchangeFeedbackViewSet(EssentialModuleViewSet):
"university",
"exchange__student_major",
"exchange__student_minor",
- "untouched"
+ "untouched",
)
required_filterset_fields = ("university",)
pagination_class = CustomPagination
diff --git a/backend/backend_app/models/partner.py b/backend/backend_app/models/partner.py
index 581068bb69ff1d82da01bffb1c5376d7e4878bad..4b4c6c70c39c1bba284f8c802a3dbf08bee06dc6 100644
--- a/backend/backend_app/models/partner.py
+++ b/backend/backend_app/models/partner.py
@@ -21,6 +21,7 @@ class Partner(BaseModel):
city = models.CharField(max_length=40, null=False)
country = models.CharField(max_length=50, null=False)
iso_code = models.CharField(max_length=2, null=False)
+ is_destination_open = models.BooleanField(null=False, default=False)
# field that will have to be set manually
university = models.ForeignKey(
diff --git a/backend/backend_app/models/university.py b/backend/backend_app/models/university.py
index cf3e00f83903ea33d73d6f9cde21d848fbe2c56e..c2a78920620937cf3a0d81201d6cc0f2ac2f6525 100644
--- a/backend/backend_app/models/university.py
+++ b/backend/backend_app/models/university.py
@@ -58,6 +58,10 @@ class UniversitySerializer(EssentialModuleSerializer):
"acronym",
"logo",
"website",
+ "city",
+ "country",
+ "main_campus_lat",
+ "main_campus_lon",
"denormalized_infos",
)
diff --git a/backend/backend_app/models/version.py b/backend/backend_app/models/version.py
index 9c4ab8726f86f6ca0cc11fd0b03ae32f3690e8da..b39e9b49ebed4ff0db6a84b4f638f6547f4e63b6 100644
--- a/backend/backend_app/models/version.py
+++ b/backend/backend_app/models/version.py
@@ -90,4 +90,4 @@ class VersionViewSet(BaseModelViewSet):
ct = ContentType.objects.get_for_id(content_type_id)
model = ct.model_class()
obj = model.objects.get(pk=object_pk)
- return Version.objects.get_for_object(obj).order_by('revision_id')
+ return Version.objects.get_for_object(obj).order_by("revision_id")
diff --git a/backend/backend_app/viewsets.py b/backend/backend_app/viewsets.py
index f4b1309e71d3b67987a45c5542e4b97c3c84b7cf..4533948fb3b8c67a0a589136899d10ba4e44387c 100644
--- a/backend/backend_app/viewsets.py
+++ b/backend/backend_app/viewsets.py
@@ -8,8 +8,6 @@ from rest_framework.viewsets import ViewSet
from backend_app.checks import check_viewsets
from backend_app.models.abstract.base import BaseModelViewSet
from backend_app.models.abstract.essentialModule import EssentialModuleViewSet
-from backend_app.models.campus import CampusViewSet, MainCampusViewSet
-from backend_app.models.city import CityViewSet
from backend_app.models.country import CountryViewSet
from backend_app.models.countryDri import CountryDriViewSet
from backend_app.models.countryScholarship import CountryScholarshipViewSet
@@ -121,9 +119,6 @@ class ExchangeViewSet(BaseModelViewSet):
ALL_API_VIEWSETS = [
SiteInformationViewSet,
UserViewset,
- CampusViewSet,
- MainCampusViewSet,
- CityViewSet,
CountryViewSet,
CountryDriViewSet,
CountryScholarshipViewSet,
diff --git a/backend/external_data/management/commands/utils.py b/backend/external_data/management/commands/utils.py
index 4e66f6ed8cb5ad065b9edccdd40ac30807e59379..e2436bb370e0c00d70898b011ab0f08d635b1e74 100644
--- a/backend/external_data/management/commands/utils.py
+++ b/backend/external_data/management/commands/utils.py
@@ -2,6 +2,7 @@ import logging
import os
import requests
+from django.db import transaction
from backend_app.models.course import Course
from backend_app.models.currency import Currency
@@ -45,25 +46,29 @@ class FixerData(object):
def update(self):
logger.info("Updating currencies from Fixer API")
- response = requests.get(
- "http://data.fixer.io/api/latest?access_key={}".format(self.FIXER_API_TOKEN)
- )
-
- data = response.json()
- if data["success"]:
- for (code, rate) in response.json()["rates"].items():
- Currency.objects.update_or_create(
- code=code, defaults={"one_EUR_in_this_currency": rate}
+ with transaction.atomic():
+ response = requests.get(
+ "http://data.fixer.io/api/latest?access_key={}".format(
+ self.FIXER_API_TOKEN
)
- ExternalDataUpdateInfo.objects.create(source="fixer")
- logger.info("Currency update was successful")
-
- else:
- logger.error(
- "Updating currency information from fixer failed, see response from the API below."
)
- logger.error(response.json())
+
+ data = response.json()
+
+ if data["success"]:
+ for (code, rate) in response.json()["rates"].items():
+ Currency.objects.update_or_create(
+ code=code, defaults={"one_EUR_in_this_currency": rate}
+ )
+ ExternalDataUpdateInfo.objects.create(source="fixer")
+ logger.info("Currency update was successful")
+
+ else:
+ logger.error(
+ "Updating currency information from fixer failed, see response from the API below."
+ )
+ logger.error(response.json())
class UtcData(object):
@@ -75,21 +80,28 @@ class UtcData(object):
def update(self):
logger.info("Updating UTC Data")
- self.update_partners()
- paged_data_walk(
- "{}/openedDestinations".format(self.UTC_API_ENDPOINT),
- self.__import_opened_destinations,
- )
- paged_data_walk(
- "{}/exchanges".format(self.UTC_API_ENDPOINT), self.__import_exchange_data
- )
- paged_data_walk(
- "{}/courses".format(self.UTC_API_ENDPOINT), self.__import_course_data
- )
- ExternalDataUpdateInfo.objects.create(source="utc")
- logger.info("Updating invalidated")
- self.__update_invalidated()
- logger.info("Updating UTC info done !")
+
+ with transaction.atomic():
+ self.update_partners()
+
+ # Reset destination open status on all partners
+ # before fetching open destinations
+ Partner.objects.all().update(is_destination_open=False)
+ paged_data_walk(
+ "{}/openedDestinations".format(self.UTC_API_ENDPOINT),
+ self.__import_opened_destinations,
+ )
+ paged_data_walk(
+ "{}/exchanges".format(self.UTC_API_ENDPOINT),
+ self.__import_exchange_data,
+ )
+ paged_data_walk(
+ "{}/courses".format(self.UTC_API_ENDPOINT), self.__import_course_data
+ )
+ ExternalDataUpdateInfo.objects.create(source="utc")
+ logger.info("Updating invalidated")
+ self.__update_invalidated()
+ logger.info("Updating UTC info done !")
update_denormalized_univ_major_minor()
update_denormalized_univ_field()
@@ -200,13 +212,14 @@ class UtcData(object):
logger.info("Importing the list of opened destinations")
for destination in response_content:
- logger.info(destination["idEtab"])
+ utc_partner_id = destination["idEtab"]
+ logger.info(utc_partner_id)
semester = destination["semestre"][:1]
year = destination["semestre"][1:]
Offer.objects.update_or_create(
- utc_partner_id=destination["idEtab"],
+ utc_partner_id=utc_partner_id,
year=year,
semester=semester,
defaults=dict(
@@ -217,6 +230,10 @@ class UtcData(object):
majors=destination["branches"],
),
)
+ # Don't forget to update the destination open status
+ Partner.objects.filter(utc_id=utc_partner_id).update(
+ is_destination_open=True
+ )
def __import_course_data(self, response_content):
logger.info("Importing courses data")
diff --git a/frontend/src/components/app/App.jsx b/frontend/src/components/app/App.jsx
index df5920ad8cd9c6364c00ded5c3522461552da34b..ebab9ad959bdc31ca24872424b9ad26f77818493 100644
--- a/frontend/src/components/app/App.jsx
+++ b/frontend/src/components/app/App.jsx
@@ -25,7 +25,6 @@ import PageAboutUnlinkedPartners from "../pages/PageAboutUnlinkedPartners";
import PageLogout from "../pages/PageLogout";
import withNetworkWrapper, { NetWrapParam } from "../../hoc/withNetworkWrapper";
import UniversityService from "../../services/data/UniversityService";
-import CityService from "../../services/data/CityService";
import CountryService from "../../services/data/CountryService";
import CurrencyService from "../../services/data/CurrencyService";
import LanguageService from "../../services/data/LanguageService";
@@ -34,15 +33,12 @@ import useOnBeforeComponentMount from "../../hooks/useOnBeforeComponentMount";
const SERVICES_TO_INITIALIZE = [
UniversityService,
- CityService,
CountryService,
CurrencyService,
LanguageService,
FilterService
];
-// import PageFiles from "../pages/PageFiles";
-
/**
* Main entry
*/
@@ -104,9 +100,7 @@ App.propTypes = {};
export default compose(
withNetworkWrapper([
new NetWrapParam("countries", "all"),
- new NetWrapParam("cities", "all"),
new NetWrapParam("universities", "all"),
- new NetWrapParam("mainCampuses", "all"),
new NetWrapParam("currencies", "all"),
new NetWrapParam("languages", "all"),
new NetWrapParam("serverModerationStatus", "all") // not needed for server moderation status
diff --git a/frontend/src/components/common/markdown/TruncatedMarkdown.jsx b/frontend/src/components/common/markdown/TruncatedMarkdown.jsx
index 507e9776c80215e9e2d818a1080c1279a3ff1e7b..eee2a310aac9b5aac003d47db016d0124cfcfaa3 100644
--- a/frontend/src/components/common/markdown/TruncatedMarkdown.jsx
+++ b/frontend/src/components/common/markdown/TruncatedMarkdown.jsx
@@ -10,11 +10,12 @@ function TruncatedMarkdown(props) {
const [truncated, setTruncated] = useState(false);
const { source, truncateFromLength, classes } = props;
const sourceAsString = source === null ? "" : source;
- const truncatable = sourceAsString.length > truncateFromLength;
+ const truncatable = sourceAsString.length > 3 * truncateFromLength;
if (!truncatable) {
return