mirror of
				https://gitlab.ub.uni-bielefeld.de/sfb1288inf/nopaque.git
				synced 2025-11-03 20:02:47 +00:00 
			
		
		
		
	Change the user session SocketIO Logic
This commit is contained in:
		@@ -2,4 +2,4 @@ from flask import Blueprint
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bp = Blueprint('main', __name__)
 | 
			
		||||
from . import events, routes
 | 
			
		||||
from . import routes
 | 
			
		||||
 
 | 
			
		||||
@@ -1,41 +0,0 @@
 | 
			
		||||
from app import hashids, socketio
 | 
			
		||||
from app.models import User
 | 
			
		||||
from flask_login import current_user
 | 
			
		||||
from flask_socketio import join_room
 | 
			
		||||
from app.decorators import socketio_login_required
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@socketio.on('users.user.get')
 | 
			
		||||
@socketio_login_required
 | 
			
		||||
def users_user_get(user_hashid):
 | 
			
		||||
    user_id = hashids.decode(user_hashid)
 | 
			
		||||
    user = User.query.get(user_id)
 | 
			
		||||
    if user is None:
 | 
			
		||||
        return {'code': 404, 'msg': 'Not found'}
 | 
			
		||||
    if not (user == current_user or current_user.is_administrator):
 | 
			
		||||
        return {'code': 403, 'msg': 'Forbidden'}
 | 
			
		||||
    # corpora = [x.to_dict() for x in user.corpora]
 | 
			
		||||
    # jobs = [x.to_dict() for x in user.jobs]
 | 
			
		||||
    # transkribus_htr_models = TranskribusHTRModel.query.filter(
 | 
			
		||||
    #     (TranskribusHTRModel.shared == True) | (TranskribusHTRModel.user == user)
 | 
			
		||||
    # ).all()
 | 
			
		||||
    # tesseract_ocr_models = TesseractOCRModel.query.filter(
 | 
			
		||||
    #     (TesseractOCRModel.shared == True) | (TesseractOCRModel.user == user)
 | 
			
		||||
    # ).all()
 | 
			
		||||
    # response = {
 | 
			
		||||
    #     'code': 200,
 | 
			
		||||
    #     'msg': 'OK',
 | 
			
		||||
    #     'payload': {
 | 
			
		||||
    #         'user': user.to_dict(),
 | 
			
		||||
    #         'corpora': corpora,
 | 
			
		||||
    #         'jobs': jobs,
 | 
			
		||||
    #         'transkribus_htr_models': transkribus_htr_models,
 | 
			
		||||
    #         'tesseract_ocr_models': tesseract_ocr_models
 | 
			
		||||
    #     }
 | 
			
		||||
    # }
 | 
			
		||||
    join_room(f'users.{user.hashid}')
 | 
			
		||||
    return {
 | 
			
		||||
        'code': 200,
 | 
			
		||||
        'msg': 'OK',
 | 
			
		||||
        'payload': user.to_dict(backrefs=True, relationships=True)
 | 
			
		||||
    }
 | 
			
		||||
@@ -1033,6 +1033,8 @@ def ressource_after_delete(mapper, connection, ressource):
 | 
			
		||||
    jsonpatch = [{'op': 'remove', 'path': ressource.jsonpatch_path}]
 | 
			
		||||
    room = f'users.{ressource.user_hashid}'
 | 
			
		||||
    socketio.emit('users.patch', jsonpatch, room=room)
 | 
			
		||||
    room = f'/users/{ressource.user_hashid}'
 | 
			
		||||
    socketio.emit('PATCH', jsonpatch, room=room)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@db.event.listens_for(Corpus, 'after_insert')
 | 
			
		||||
@@ -1047,8 +1049,8 @@ def ressource_after_insert_handler(mapper, connection, ressource):
 | 
			
		||||
    jsonpatch = [
 | 
			
		||||
        {'op': 'add', 'path': ressource.jsonpatch_path, 'value': value}
 | 
			
		||||
    ]
 | 
			
		||||
    room = f'users.{ressource.user_hashid}'
 | 
			
		||||
    socketio.emit('users.patch', jsonpatch, room=room)
 | 
			
		||||
    room = f'/users/{ressource.user_hashid}'
 | 
			
		||||
    socketio.emit('PATCH', jsonpatch, room=room)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@db.event.listens_for(Corpus, 'after_update')
 | 
			
		||||
@@ -1077,8 +1079,8 @@ def ressource_after_update_handler(mapper, connection, ressource):
 | 
			
		||||
            }
 | 
			
		||||
        )
 | 
			
		||||
    if jsonpatch:
 | 
			
		||||
        room = f'users.{ressource.user_hashid}'
 | 
			
		||||
        socketio.emit('users.patch', jsonpatch, room=room)
 | 
			
		||||
        room = f'/users/{ressource.user_hashid}'
 | 
			
		||||
        socketio.emit('PATCH', jsonpatch, room=room)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@db.event.listens_for(Job, 'after_update')
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,30 @@
 | 
			
		||||
class App {
 | 
			
		||||
  constructor() {
 | 
			
		||||
    this.data = {users: {}};
 | 
			
		||||
    this.eventListeners = {'users.patch': []};
 | 
			
		||||
    this.promises = {users: {}};
 | 
			
		||||
    this.socket = io({transports: ['websocket'], upgrade: false});
 | 
			
		||||
    this.socket.on('users.patch', patch => this.usersPatchHandler(patch));
 | 
			
		||||
    this.socket.on('PATCH', (patch) => {this.data = jsonpatch.applyPatch(this.data, patch).newDocument;});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  get users() {
 | 
			
		||||
    return this.data.users;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addEventListener(type, listener) {
 | 
			
		||||
    if (!(type in this.eventListeners)) {
 | 
			
		||||
      throw `Unknown event type: ${type}`;
 | 
			
		||||
  subscribeUser(userId) {
 | 
			
		||||
    if (userId in this.promises.users) {
 | 
			
		||||
      return this.promises.users[userId];
 | 
			
		||||
    }
 | 
			
		||||
    this.eventListeners[type].push(listener);
 | 
			
		||||
    this.promises.users[userId] = new Promise((resolve, reject) => {
 | 
			
		||||
      this.socket.emit('SUBSCRIBE /users/<user_id>', userId, response => {
 | 
			
		||||
        if (response.code === 200) {
 | 
			
		||||
          this.data.users[userId] = response.payload;
 | 
			
		||||
          resolve(this.data.users[userId]);
 | 
			
		||||
        } else {
 | 
			
		||||
          reject(response);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    return this.promises.users[userId];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  flash(message, category) {
 | 
			
		||||
@@ -50,29 +59,4 @@ class App {
 | 
			
		||||
    toastCloseActionElement = toast.el.querySelector('.toast-action[data-action="close"]');
 | 
			
		||||
    toastCloseActionElement.addEventListener('click', () => {toast.dismiss();});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getUserById(userId) {
 | 
			
		||||
    if (userId in this.promises.users) {
 | 
			
		||||
      return this.promises.users[userId];
 | 
			
		||||
    }
 | 
			
		||||
    this.promises.users[userId] = new Promise((resolve, reject) => {
 | 
			
		||||
      this.socket.emit('users.user.get', userId, response => {
 | 
			
		||||
        if (response.code === 200) {
 | 
			
		||||
          this.data.users[userId] = response.payload;
 | 
			
		||||
          resolve(this.data.users[userId]);
 | 
			
		||||
        } else {
 | 
			
		||||
          reject(response);
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
    return this.promises.users[userId];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  usersPatchHandler(patch) {
 | 
			
		||||
    let listener;
 | 
			
		||||
 | 
			
		||||
    this.data = jsonpatch.applyPatch(this.data, patch).newDocument;
 | 
			
		||||
    //this.data = jsonpatch.apply_patch(this.data, patch);
 | 
			
		||||
    for (listener of this.eventListeners['users.patch']) {listener(patch);}
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,16 +1,17 @@
 | 
			
		||||
class JobStatusNotifier {
 | 
			
		||||
  constructor(userId) {
 | 
			
		||||
    this.userId = userId;
 | 
			
		||||
    app.socket.on('PATCH', (patch) => {this.onPATCH(patch);});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  usersPatchHandler(patch) {
 | 
			
		||||
  onPATCH(patch) {
 | 
			
		||||
    let filteredPatch;
 | 
			
		||||
    let jobId;
 | 
			
		||||
    let match;
 | 
			
		||||
    let operation;
 | 
			
		||||
    let re;
 | 
			
		||||
 | 
			
		||||
    re = new RegExp(`^/users/${this.userId}/jobs/([A-Za-z0-9]*)/status$`)
 | 
			
		||||
    re = new RegExp(`^/users/${this.userId}/jobs/([A-Za-z0-9]*)/status$`);
 | 
			
		||||
    filteredPatch = patch
 | 
			
		||||
      .filter(operation => operation.op === 'replace')
 | 
			
		||||
      .filter(operation => re.test(operation.path));
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,7 @@ class CorpusDisplay extends RessourceDisplay {
 | 
			
		||||
    this.setNumTokens(corpus.num_tokens);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  usersPatchHandler(patch) {
 | 
			
		||||
  onPATCH(patch) {
 | 
			
		||||
    let filteredPatch;
 | 
			
		||||
    let operation;
 | 
			
		||||
    let re;
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,7 @@ class JobDisplay extends RessourceDisplay {
 | 
			
		||||
    this.setTitle(job.title);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  usersPatchHandler(patch) {
 | 
			
		||||
  onPATCH(patch) {
 | 
			
		||||
    let filteredPatch;
 | 
			
		||||
    let operation;
 | 
			
		||||
    let re;
 | 
			
		||||
 
 | 
			
		||||
@@ -2,13 +2,13 @@ class RessourceDisplay {
 | 
			
		||||
  constructor(displayElement) {
 | 
			
		||||
    this.displayElement = displayElement;
 | 
			
		||||
    this.userId = this.displayElement.dataset.userId;
 | 
			
		||||
    app.addEventListener('users.patch', patch => this.usersPatchHandler(patch));
 | 
			
		||||
    app.getUserById(this.userId).then(user => this.init(user));
 | 
			
		||||
    app.socket.on('PATCH', (patch) => {this.onPATCH(patch);});
 | 
			
		||||
    app.subscribeUser(this.userId).then((user) => {this.init(user);});
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init(user) {throw 'Not implemented';}
 | 
			
		||||
 | 
			
		||||
  usersPatchHandler(patch) {throw 'Not implemented';}
 | 
			
		||||
  onPATCH(patch) {throw 'Not implemented';}
 | 
			
		||||
 | 
			
		||||
  setElement(element, value) {
 | 
			
		||||
    switch (element.tagName) {
 | 
			
		||||
 
 | 
			
		||||
@@ -96,7 +96,7 @@ class CorpusFileList extends RessourceList {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  usersPatchHandler(patch) {
 | 
			
		||||
  onPATCH(patch) {
 | 
			
		||||
    let corpusFileId;
 | 
			
		||||
    let filteredPatch;
 | 
			
		||||
    let match;
 | 
			
		||||
 
 | 
			
		||||
@@ -88,7 +88,7 @@ class CorpusList extends RessourceList {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  usersPatchHandler(patch) {
 | 
			
		||||
  onPATCH(patch) {
 | 
			
		||||
    let corpusId;
 | 
			
		||||
    let filteredPatch;
 | 
			
		||||
    let match;
 | 
			
		||||
 
 | 
			
		||||
@@ -54,5 +54,5 @@ class JobInputList extends RessourceList {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  usersPatchHandler(patch) {return;}
 | 
			
		||||
  onPATCH(patch) {return;}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,7 @@ class JobList extends RessourceList {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  usersPatchHandler(patch) {
 | 
			
		||||
  onPATCH(patch) {
 | 
			
		||||
    let filteredPatch;
 | 
			
		||||
    let jobId;
 | 
			
		||||
    let match;
 | 
			
		||||
 
 | 
			
		||||
@@ -57,7 +57,7 @@ class JobResultList extends RessourceList {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  usersPatchHandler(patch) {
 | 
			
		||||
  onPATCH(patch) {
 | 
			
		||||
    let filteredPatch;
 | 
			
		||||
    let operation;
 | 
			
		||||
    let re;
 | 
			
		||||
 
 | 
			
		||||
@@ -89,7 +89,7 @@ class QueryResultList extends RessourceList {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  usersPatchHandler(patch) {
 | 
			
		||||
  onPATCH(patch) {
 | 
			
		||||
    let filteredPatch;
 | 
			
		||||
    let match;
 | 
			
		||||
    let operation;
 | 
			
		||||
 
 | 
			
		||||
@@ -91,10 +91,10 @@ class RessourceList {
 | 
			
		||||
    this.userId = this.listjs.listContainer.dataset.userId;
 | 
			
		||||
    this.listjs.list.addEventListener('click', event => this.onclick(event));
 | 
			
		||||
    if (this.userId) {
 | 
			
		||||
      app.addEventListener('users.patch', patch => this.usersPatchHandler(patch));
 | 
			
		||||
      app.getUserById(this.userId).then(
 | 
			
		||||
        user => this.init(user),
 | 
			
		||||
        error => {throw JSON.stringify(error);}
 | 
			
		||||
      app.socket.on('PATCH', (patch) => {this.onPATCH(patch);});
 | 
			
		||||
      app.subscribeUser(this.userId).then(
 | 
			
		||||
        (user) => {this.init(user);},
 | 
			
		||||
        (error) => {throw JSON.stringify(error);}
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -117,7 +117,7 @@ class RessourceList {
 | 
			
		||||
 | 
			
		||||
  onclick(event) {throw 'Not implemented';}
 | 
			
		||||
 | 
			
		||||
  usersPatchHandler(patch) {throw 'Not implemented';}
 | 
			
		||||
  onPATCH(patch) {throw 'Not implemented';}
 | 
			
		||||
 | 
			
		||||
  add(ressources) {
 | 
			
		||||
    let values = Array.isArray(ressources) ? ressources : [ressources];
 | 
			
		||||
 
 | 
			
		||||
@@ -32,11 +32,10 @@
 | 
			
		||||
  const jobStatusNotifier = new JobStatusNotifier(currentUserId);
 | 
			
		||||
 | 
			
		||||
  // Initialize components for current user
 | 
			
		||||
  app.addEventListener('users.patch', patch => jobStatusNotifier.usersPatchHandler(patch));
 | 
			
		||||
  app.getUserById(currentUserId)
 | 
			
		||||
  app.subscribeUser(currentUserId)
 | 
			
		||||
    .then(
 | 
			
		||||
      user => {return;},
 | 
			
		||||
      error => {throw JSON.stringify(error);}
 | 
			
		||||
      (user) => {return;},
 | 
			
		||||
      (error) => {throw JSON.stringify(error);}
 | 
			
		||||
    );
 | 
			
		||||
  {%- endif %}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										69
									
								
								app/templates/users/users.html.j2
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								app/templates/users/users.html.j2
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
			
		||||
{% extends "base.html.j2" %}
 | 
			
		||||
 | 
			
		||||
{% block page_content %}
 | 
			
		||||
<div class="container">
 | 
			
		||||
  <div class="row">
 | 
			
		||||
    <div class="col s12">
 | 
			
		||||
      <h1 id="title">{{ title }}</h1>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="col s12">
 | 
			
		||||
      <div class="card">
 | 
			
		||||
        <div class="card-content">
 | 
			
		||||
          <table class="" id="users"></table>
 | 
			
		||||
        </div>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </div>
 | 
			
		||||
</div>
 | 
			
		||||
{% endblock page_content %}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
{% block scripts %}
 | 
			
		||||
{{ super() }}
 | 
			
		||||
<script src="https://unpkg.com/gridjs/dist/gridjs.umd.js"></script>
 | 
			
		||||
<script>
 | 
			
		||||
  const updateUrl = (prev, query) => {
 | 
			
		||||
    return prev + (prev.indexOf('?') >= 0 ? '&' : '?') + new URLSearchParams(query).toString();
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  new gridjs.Grid({
 | 
			
		||||
    columns: [
 | 
			
		||||
      { id: 'username', name: 'Username' },
 | 
			
		||||
      { id: 'email', name: 'Email' },
 | 
			
		||||
    ],
 | 
			
		||||
    server: {
 | 
			
		||||
      url: '/users/api_users',
 | 
			
		||||
      then: results => results.data,
 | 
			
		||||
      total: results => results.total,
 | 
			
		||||
    },
 | 
			
		||||
    search: {
 | 
			
		||||
      enabled: true,
 | 
			
		||||
      server: {
 | 
			
		||||
        url: (prev, search) => {
 | 
			
		||||
          return updateUrl(prev, {search});
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    sort: {
 | 
			
		||||
      enabled: true,
 | 
			
		||||
      multiColumn: true,
 | 
			
		||||
      server: {
 | 
			
		||||
        url: (prev, columns) => {
 | 
			
		||||
          const columnIds = ['username', 'email'];
 | 
			
		||||
          const sort = columns.map(col => (col.direction === 1 ? '+' : '-') + columnIds[col.index]);
 | 
			
		||||
          return updateUrl(prev, {sort});
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    },
 | 
			
		||||
    pagination: {
 | 
			
		||||
      enabled: true,
 | 
			
		||||
      server: {
 | 
			
		||||
        url: (prev, page, limit) => {
 | 
			
		||||
          return updateUrl(prev, {offset: page * limit, limit: limit});
 | 
			
		||||
        },
 | 
			
		||||
      },
 | 
			
		||||
    }
 | 
			
		||||
  }).render(document.getElementById('users'));
 | 
			
		||||
</script>
 | 
			
		||||
{% endblock scripts %}
 | 
			
		||||
							
								
								
									
										5
									
								
								app/users/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/users/__init__.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,5 @@
 | 
			
		||||
from flask import Blueprint
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
bp = Blueprint('users', __name__)
 | 
			
		||||
from . import events, routes  # noqa
 | 
			
		||||
							
								
								
									
										32
									
								
								app/users/events.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								app/users/events.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
			
		||||
from app import hashids, socketio
 | 
			
		||||
from app.decorators import socketio_login_required
 | 
			
		||||
from app.models import User
 | 
			
		||||
from flask_login import current_user
 | 
			
		||||
from flask_socketio import join_room, leave_room
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@socketio.on('SUBSCRIBE /users/<user_id>')
 | 
			
		||||
@socketio_login_required
 | 
			
		||||
def subscribe_user(user_hashid):
 | 
			
		||||
    user_id = hashids.decode(user_hashid)
 | 
			
		||||
    user = User.query.get(user_id)
 | 
			
		||||
    if user is None:
 | 
			
		||||
        return {'code': 404, 'msg': 'Not found'}
 | 
			
		||||
    if not (user == current_user or current_user.is_administrator):
 | 
			
		||||
        return {'code': 403, 'msg': 'Forbidden'}
 | 
			
		||||
    dict_user = user.to_dict(backrefs=True, relationships=True)
 | 
			
		||||
    join_room(f'/users/{user.hashid}')
 | 
			
		||||
    return {'code': 200, 'msg': 'OK', 'payload': dict_user}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@socketio.on('UNSUBSCRIBE /users/<user_id>')
 | 
			
		||||
@socketio_login_required
 | 
			
		||||
def subscribe_user(user_hashid):
 | 
			
		||||
    user_id = hashids.decode(user_hashid)
 | 
			
		||||
    user = User.query.get(user_id)
 | 
			
		||||
    if user is None:
 | 
			
		||||
        return {'code': 404, 'msg': 'Not found'}
 | 
			
		||||
    if not (user == current_user or current_user.is_administrator):
 | 
			
		||||
        return {'code': 403, 'msg': 'Forbidden'}
 | 
			
		||||
    leave_room(f'/users/{user.hashid}')
 | 
			
		||||
    return {'code': 200, 'msg': 'OK'}
 | 
			
		||||
							
								
								
									
										50
									
								
								app/users/routes.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								app/users/routes.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
from app.models import User
 | 
			
		||||
from flask import render_template, request, url_for
 | 
			
		||||
from . import bp
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/')
 | 
			
		||||
def users():
 | 
			
		||||
    return render_template(
 | 
			
		||||
        'users/users.html.j2',
 | 
			
		||||
        title='Users'
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@bp.route('/api_users')
 | 
			
		||||
def api_users():
 | 
			
		||||
    query = User.query
 | 
			
		||||
 | 
			
		||||
    # search filter
 | 
			
		||||
    search = request.args.get('search')
 | 
			
		||||
    if search:
 | 
			
		||||
        query = query.filter(User.username.like(f'%{search}%') | User.email.like(f'%{search}%'))
 | 
			
		||||
    total = query.count()
 | 
			
		||||
 | 
			
		||||
    # sorting
 | 
			
		||||
    sort = request.args.get('sort')
 | 
			
		||||
    if sort:
 | 
			
		||||
        order = []
 | 
			
		||||
        for s in sort.split(','):
 | 
			
		||||
            direction = s[0]
 | 
			
		||||
            name = s[1:]
 | 
			
		||||
            if name not in ['username', 'email']:
 | 
			
		||||
                name = 'username'
 | 
			
		||||
            col = getattr(User, name)
 | 
			
		||||
            if direction == '-':
 | 
			
		||||
                col = col.desc()
 | 
			
		||||
            order.append(col)
 | 
			
		||||
        if order:
 | 
			
		||||
            query = query.order_by(*order)
 | 
			
		||||
 | 
			
		||||
    # pagination
 | 
			
		||||
    offset = request.args.get('offset', type=int, default=-1)
 | 
			
		||||
    limit = request.args.get('limit', type=int, default=-1)
 | 
			
		||||
    if offset != -1 and limit != -1:
 | 
			
		||||
        query = query.offset(offset).limit(limit)
 | 
			
		||||
 | 
			
		||||
    # response
 | 
			
		||||
    return {
 | 
			
		||||
        'data': [user.to_dict() for user in query],
 | 
			
		||||
        'total': total
 | 
			
		||||
    }
 | 
			
		||||
		Reference in New Issue
	
	Block a user