Browse Source

Last version of webapp and add of docker instance

Jérôme BUISINE 7 months ago
parent
commit
df95e1299e

+ 3 - 2
.gitignore

@@ -104,6 +104,7 @@ venv.bak/
 .mypy_cache/
 
 # data folder
-data
-expe
+links/data
+links/expe
 .vscode
+media

+ 26 - 0
Dockerfile

@@ -0,0 +1,26 @@
+FROM python
+
+COPY . /usr/src/app
+WORKDIR /usr/src/app
+
+# Server port
+EXPOSE 8000
+
+RUN apt-get update
+
+# update project source code if necessary (use by default https protocol)
+RUN git remote set-url origin https://github.com/prise-3d/SIN3D-launcher.git
+RUN git pull origin master
+
+# Install dependencies and prepare project
+RUN python --version
+RUN pip install -r requirements.txt
+RUN python manage.py makemigrations
+RUN python manage.py migrate
+
+# only comment in case it will be necessary
+# RUN echo $WEBEXPE_PREFIX_URL
+# RUN WEBEXPE_PREFIX_URL=$WEBEXPE_PREFIX_URL
+# RUN WEB_API_PREFIX_URL=$WEB_API_PREFIX_URL
+
+CMD ["python", "manage.py", "runserver", "0.0.0.0:8000"]

+ 48 - 0
Makefile

@@ -0,0 +1,48 @@
+build:
+	@echo "----------------------------------------------------------------"
+	@echo "Update of sin3dlauncher image"
+	@echo "----------------------------------------------------------------"
+	docker build --no-cache . --tag sin3dlauncher
+	@echo "----------------------------------------------------------------"
+	@echo "Image is now build you can run instance using 'make run'"
+	@echo "----------------------------------------------------------------"
+
+run: 
+	@echo "----------------------------------------------------------------"
+	@echo "Process to run new instance"
+	@echo "----------------------------------------------------------------"
+	docker-compose up
+	@echo "----------------------------------------------------------------"
+	@echo "Your docker instance is now launched with name 'sin3dlauncherinst'"
+	@echo "Your website is now accessible at http://localhost:8000"
+	@echo "----------------------------------------------------------------"
+
+stop:
+	@echo "----------------------------------------------------------------"
+	@echo "Process to stop current instance"
+	@echo "----------------------------------------------------------------"
+	docker stop sin3dlauncherinst
+	@echo "----------------------------------------------------------------"
+	@echo "App is now stopped"
+	@echo "----------------------------------------------------------------"
+
+remove:
+	@echo "----------------------------------------------------------------"
+	@echo "Process to stop current instance"
+	@echo "----------------------------------------------------------------"
+	docker stop sin3dlauncherinst
+	docker rm sin3dlauncherinst
+	@echo "----------------------------------------------------------------"
+	@echo "App is now stopped and removed"
+	@echo "----------------------------------------------------------------"
+
+clean: 
+	@echo "----------------------------------------------------------------"
+	@echo "Process to remove image"
+	@echo "----------------------------------------------------------------"
+	docker rmi sin3dlauncher
+	@echo "----------------------------------------------------------------"
+	@echo "sin3dlauncher image is now deleted"
+	@echo "----------------------------------------------------------------"
+
+deploy: build run

+ 6 - 2
README.md

@@ -15,12 +15,12 @@ pip install -r requirements.txt
 
 Generate user links (file saved into `data` folder):
 ```sh
-python generate/generate_experiment_link.py --experiment AreSameImagesRandom --experimentId expe1 --scenes Appart1opt02,Bureau1,Cendrier --output expe1.csv
+python links/generate/generate_experiment_link.py --experiment AreSameImagesRandom --experimentId expe1 --scenes Appart1opt02,Bureau1,Cendrier --output expe1.csv
 ```
 
 Generate experiment links for each user (file saved into `expe` folder):
 ```sh
-python generate/generate_experiment.py --data data/expe1.csv --scenes 2 --users 150 --output expe1_user_links.csv
+python links/generate/generate_experiment.py --data data/expe1.csv --scenes 2 --users 150 --userId 1 --output expe1_user_links.csv
 ```
 
 The final file is generated and contains data just as follow:
@@ -33,6 +33,10 @@ link2;link3;...;linkN
 
 ## Launch WebApp
 
+```
+python manage.py migrate
+```
+
 ```
 python manage.py runserver
 ```

+ 17 - 0
docker-compose.yml

@@ -0,0 +1,17 @@
+version: '3'
+
+services:
+
+    webexpe:
+        container_name: sin3dlauncherinst
+        image: sin3dlauncher
+        restart: always
+        volumes:
+            - "./media:/usr/src/app/media" # get access to media files
+        ports:
+           - "8000:8000"
+        
+        # only comment in case it will be necessary
+        # environment:
+        #    WEBEXPE_PREFIX_URL: "${WEBEXPE_PREFIX_URL:-}"
+        #    WEB_API_PREFIX_URL: "${WEB_API_PREFIX_URL:-api}"

+ 2 - 2
config.py

@@ -10,5 +10,5 @@ experiment_list = [
 
 default_host = 'https://diran.univ-littoral.fr'
 
-links_data_folder = 'media/data'
-expe_data_folder  = 'media/expe'
+links_data_folder = 'links/media/data'
+expe_data_folder  = 'links/media/expe'

+ 0 - 0
links/generate/__init__.py


+ 52 - 8
generate/generate_experiment.py

@@ -10,7 +10,28 @@ import random
 # modules imports
 sys.path.insert(0, '') # trick to enable import of main folder module
 
-import config  as cfg
+# config imports
+import links.config  as cfg
+
+
+def decoded_data(data):
+    decoded_data = str(base64.b64decode(data), "utf-8")
+
+    return decoded_data
+
+
+def encode_data(data):
+    json_data = json.dumps(data)
+    link_data = base64.b64encode(str(json_data).encode('utf-8'))
+    
+    return link_data
+
+
+def extract_data(line):
+
+    data = line.replace('\n', '').split(';')
+
+    return (data[0], data[-1])
 
 
 def main():
@@ -20,6 +41,7 @@ def main():
     parser.add_argument('--data', type=str, help='data links to use', required=True)
     parser.add_argument('--scenes', type=int, help="number of scenes", required=True)
     parser.add_argument('--users', type=int, help="number of users of experiment", required=True)
+    parser.add_argument('--userId', type=int, help="tell if user identifier is used or not", required=False, default=0)
     parser.add_argument('--output', type=str, help="output filename of user links", required=True)
 
     args = parser.parse_args()
@@ -27,12 +49,15 @@ def main():
     p_data          = args.data
     p_scenes        = args.scenes
     p_users         = args.users
+    p_userId        = bool(args.userId)
     p_output        = args.output
 
+    print(p_userId)
+
     # generate link for each scene
     with open(p_data, 'r') as f:
         lines = f.readlines()
-        lines = [l.replace('\n', '') for l in lines]
+        data_lines = [extract_data(l) for l in lines]
         
         nb_elements = len(lines)
 
@@ -48,14 +73,33 @@ def main():
 
         output_f = open(filename_path, 'w')
 
-        for _ in range(p_users):
-
-            user_links = random.choices(lines, k=p_scenes)
+        for i in range(p_users):
+            
+            scene_links = random.sample(data_lines, k=p_scenes)
 
             # generate output line
-            output_line = ""
-            for link in user_links:
-                output_line += link + ';'
+            output_line = str(i) + ';' 
+            for scene_name, link in scene_links:
+
+                if p_userId:
+                    data = link.split('?q=')
+
+                    hostname = data[0]
+                    link_data = data[1]
+
+                    # decode and add user id link if asked
+                    decoded_data_link = decoded_data(link_data)
+                    json_data = json.loads(decoded_data_link)
+                    json_data['userId'] = str(i)
+                    encoded_data_link = encode_data(json_data)
+                    new_link = hostname + '?q=' + str(encoded_data_link, "utf-8")
+
+                    # add new link
+                    output_line += scene_name + ':::' + new_link + ';'
+                else:
+                    output_line += scene_name + ':::' + link + ';'
+
+
             output_line += '\n'
 
             output_f.write(output_line)

+ 4 - 3
generate/generate_experiment_link.py

@@ -9,7 +9,8 @@ import requests
 # modules imports
 sys.path.insert(0, '') # trick to enable import of main folder module
 
-import config  as cfg
+# config imports
+import links.config  as cfg
 
 
 def encode_data(data):
@@ -66,8 +67,8 @@ def main():
 
     with open(filename_path, 'w') as f:
 
-        for link in links:
-            f.write(link + '\n')
+        for id, link in enumerate(links):
+            f.write(p_scenes[id] + ';' + p_experiment + ';' + p_experiment_id + ';' + link + '\n')
 
     print("Links are saved into.. %s" % filename_path)
 

+ 35 - 4
links/templates/links/files.html

@@ -7,10 +7,41 @@
 {% endblock %}
 
 {% block content %}
+    
+    <h3>Links files of experiments</h3>
+    <br />
 
-    We will display list of files here
+    <div class="files-expe">
 
-    {% block javascripts %}
- 
-    {% endblock %}
+        <br/>
+        {% if folder %}
+
+            <!-- List of items which has identifier when user pass experiment -->
+            <h3>List of files</h3>
+            <ul class="list-group">
+
+                {% for file in folder %} 
+                                    
+                    <li class="list-group-item">
+                        
+                        <div class="row">
+                            <div class="col-md-11">{{file}} </div>
+                            <div class="col-md-1 files-list" data-redirect-path="{{file}}"><i class="fas fa-arrow-circle-right"></i></div>
+                        </div>
+                    </li>
+                {% endfor %} 
+            </ul>
+
+
+        {% else %}
+            <div class="alert alert-warning" role="alert">
+                <i>Experiment folder is empty</i>
+            </div>
+        {% endif %}
+    </div>
 {% endblock %}
+
+{% block javascripts %}
+    <script src="{% static "js/files.js" %}"></script>
+{% endblock %}
+

+ 41 - 0
links/templates/links/links.html

@@ -0,0 +1,41 @@
+{% extends 'base.html' %}
+
+{% load staticfiles %}
+
+{% block title %}
+    Links list
+{% endblock %}
+
+{% block content %}
+    
+    <h3>Select your user identifier</h3>
+    <br />
+
+    <div class="row">
+        <div class="col-md-4 offset-md-4">
+            <form method="GET" action="/expe">
+                <div class="form-group">
+                    <input type="number" min="0" class="form-control" name="userId" placeholder="Enter your user identifier"/>
+                </div>
+            </form>
+        </div>
+    </div>
+
+    <br />
+    <hr />
+    <br />
+
+    <ul class="list-group" id="links-list">
+    
+    </ul>
+
+{% endblock %}
+
+{% block javascripts %}
+    <script type="text/javascript"> 
+        // Utils informations
+        var links = "{{links}}"
+    </script>
+    <script src="{% static "js/links.js" %}"></script>
+{% endblock %}
+

+ 1 - 0
links/urls.py

@@ -9,6 +9,7 @@ app_name = 'expe'
 
 urlpatterns = [
     path('', views.list_files, name='list_files'),
+    path('links', views.user_links, name='user_links'),
 ]
 
 if settings.DEBUG is True:

+ 38 - 1
links/views.py

@@ -7,6 +7,9 @@ from django.http import Http404
 
 # main imports
 import os
+import json
+
+from . import config  as cfg
 
 
 def list_files(request):
@@ -14,13 +17,47 @@ def list_files(request):
     # get param 
     # TODO : implement view which list all expe links file
 
-    data = {
+    experiment_path = cfg.expe_data_folder
 
+    files = sorted(os.listdir(experiment_path))
+
+    data = {
+        'folder': files
     }
 
     return render(request, 'links/files.html', data)
 
 
+def user_links(request):
+
+    filename = request.GET.get('filename')
+
+    if filename is None:
+        # send 404 error
+        raise Http404("Page does not exist")
+    
+    filepath = os.path.join(cfg.expe_data_folder, filename)
+
+    if not os.path.exists(filepath):
+        # send 404 error
+        raise Http404("File asked does not exist")
+
+    # read data and send it
+    with open(filepath, 'r') as f:
+        lines = [l.replace('\n', '') for l in f.readlines()]
+
+    links = {}
+    for line in lines:
+        data = line.split(';')
+        links[data[0]] = data[1:]
+            
+    data = {
+        'links': json.dumps(links)
+    }
+    
+    return render(request, 'links/links.html', data)
+
+
 
 
 

+ 26 - 0
static/css/links.css

@@ -1,3 +1,29 @@
 body {
     background-color: lightgrey;
+}
+
+.files-expe{
+    margin-left: 20%;
+    margin-right: 20%;
+    text-align: left;
+}
+
+.already-used{
+    background-color: lightslategrey;
+}
+
+.already-used a{
+    color:black;
+}
+
+.already-used a:hover{
+    text-decoration: none;
+}
+
+.files-expe a:hover{
+    text-decoration: none;
+}
+
+.files-expe a{
+    color:black;
 }

+ 18 - 0
static/js/files.js

@@ -0,0 +1,18 @@
+const toggleVisible = ele => ele.style.display = ele.style.display === 'none' ? 'block' : 'none'
+const toggleClass = (ele, class1, class2) => ele.className = ele.className === class1 ? class2 : class1
+const baseUrl    = location.protocol + '//' + window.location.host + '/'
+window.addEventListener('DOMContentLoaded', () => {
+    // Display list of files from day folder
+    // need to parse as `Array`
+    Array.from(document.getElementsByClassName('files-list')).forEach(item => {
+        item.addEventListener('click', event => {
+            event.preventDefault()
+            currentElem = event.currentTarget
+
+            // get list element
+            let filePath = currentElem.getAttribute('data-redirect-path')
+
+            window.location = baseUrl + 'links?filename=' + filePath
+        })
+    })
+})

+ 98 - 0
static/js/links.js

@@ -0,0 +1,98 @@
+const links_data = JSON.parse(links.replace(/&quot;/g, '"'))
+
+
+function loadDataList(elem, list){
+    userId = elem.value
+
+    if (userId){
+        let currentLinks = links_data[userId]
+        
+        // remove event listener of each element by default
+        if (list.children.length > 0){
+            for (var element of list.children){
+                element.removeEventListener('click', elemClick)
+            }
+        }
+
+        list.innerHTML = ""
+    
+        currentLinks.forEach((element, index) => {
+    
+            if (element.length > 0){
+    
+                data = element.split(':::')
+
+                // add of div elements
+                rowDiv = document.createElement('div')
+                rowDiv.setAttribute('class', 'row')
+
+                rowDivLeft = document.createElement('div')
+                rowDivLeft.setAttribute('class', 'col-md-11')
+
+                rowDivRight = document.createElement('div')
+                rowDivRight.setAttribute('class', 'col-md-1')
+
+                // create link
+                currentLink = document.createElement('a')
+                currentLink.setAttribute('href', data[1])
+                currentLink.innerHTML = 'Link ' + (index + 1) + ': ' + data[0]
+                
+                // add of elements
+                rowDivLeft.appendChild(currentLink)
+                rowDiv.appendChild(rowDivLeft)
+                rowDiv.appendChild(rowDivRight)
+
+                currentLi = document.createElement('li')
+                currentLi.setAttribute('class', 'list-group-item')
+                currentLi.appendChild(rowDiv)
+
+                list.appendChild(currentLi)
+            }
+        });
+    }
+}
+
+function elemClick(event){
+    event.preventDefault()
+    
+    currentElem = event.currentTarget
+    
+    // Add <i class="fas fa-check"></i>
+    divLeft = currentElem.getElementsByClassName('col-md-1')[0]
+
+    if (divLeft.children.length <= 0){
+        iconElem = document.createElement('li')
+        iconElem.setAttribute('class', 'fas fa-check')
+        divLeft.appendChild(iconElem)
+
+        // update `li` class element
+        currentElem.setAttribute('class', 'list-group-item already-used')
+    }
+
+    // retrieve and open link in new tab
+    link = currentElem.getElementsByTagName('a')[0]
+    url = link.getAttribute('href')
+    var win = window.open(url, '_blank');
+    win.focus();
+}
+
+window.addEventListener('DOMContentLoaded', () => {
+    // Display list of files from day folder
+    // need to parse as `Array`
+
+    const inputElement = document.getElementsByName('userId')[0]
+    const linksList = document.getElementById('links-list')
+
+    loadDataList(inputElement, linksList)
+    
+    inputElement.addEventListener('change', event => {
+        event.preventDefault()
+        currentElem = event.currentTarget
+
+        loadDataList(currentElem, linksList)
+
+        for (var element of linksList.children){
+            element.addEventListener('click', elemClick)
+        }
+    })
+})