mirror of
https://github.com/inventree/InvenTree
synced 2024-08-30 18:33:04 +00:00
Part page loading improvements (#3185)
* Lazy load the pricing bom table when the "pricing" tab is selected * Update django-debug-toolbar configuration * Major refactoring for the 'can_build' function - Use a single annotated query to the db, rather than a for loop (which is what a caveman would use) - Query performance is greatly improved - Also refactors existing variant-part-stock subquery code, to make it re-usable * Use minified JS and CSS where possible * Render a 'preview' version of each part image - Saves load time when the image is quite large - Adds a data migration to render out the new variation * Adds 'preview' version of company images * Defer loading of javascript files Note: some cannot be deferred - jquery in particular * Crucial bugfix for user roles context - Previously was *not* being calculated correctly - A non-superuser role would most likely display pages incorrectly * Prevent loading of "about" on every page - Load dynamically when requested - Takes ~400ms! - Cuts out a lot of fat * Match displayed image size to preview image size * Utilize caching framework for accessing user "role" information - Reduces number of DB queries required by rendering framework * Remove redundant query elements * Remove 'stock' field from PartBrief serializer - A calculated field on a serializer is a *bad idea* when that calculation requires a DB hit * Query improvements for StockItem serializer - Remove calculated fields - Fix annotations * Bug fixes * Remove JS load test - Loading of JS files is now deferred, so the unit test does not work as it used to * Fix broken template for "maintenance" page * Remove thumbnail generation migrations - Already performed manually as part of ''invoke migrate" - Running as a migration causes unit test problems - Not sensible to run this as a data-migration anyway * tweak for build table
This commit is contained in:
parent
0d01ea2f2e
commit
74bec86675
@ -6,7 +6,7 @@ import InvenTree.status
|
||||
from InvenTree.status_codes import (BuildStatus, PurchaseOrderStatus,
|
||||
SalesOrderStatus, StockHistoryCode,
|
||||
StockStatus)
|
||||
from users.models import RuleSet
|
||||
from users.models import RuleSet, check_user_role
|
||||
|
||||
|
||||
def health_status(request):
|
||||
@ -83,31 +83,13 @@ def user_roles(request):
|
||||
roles = {
|
||||
}
|
||||
|
||||
if user.is_superuser:
|
||||
for ruleset in RuleSet.RULESET_MODELS.keys(): # pragma: no cover
|
||||
roles[ruleset] = {
|
||||
'view': True,
|
||||
'add': True,
|
||||
'change': True,
|
||||
'delete': True,
|
||||
}
|
||||
else:
|
||||
for group in user.groups.all():
|
||||
for rule in group.rule_sets.all():
|
||||
for role in RuleSet.RULESET_MODELS.keys():
|
||||
|
||||
# Ensure the role name is in the dict
|
||||
if rule.name not in roles:
|
||||
roles[rule.name] = {
|
||||
'view': user.is_superuser,
|
||||
'add': user.is_superuser,
|
||||
'change': user.is_superuser,
|
||||
'delete': user.is_superuser
|
||||
}
|
||||
permissions = {}
|
||||
|
||||
# Roles are additive across groups
|
||||
roles[rule.name]['view'] |= rule.can_view
|
||||
roles[rule.name]['add'] |= rule.can_add
|
||||
roles[rule.name]['change'] |= rule.can_change
|
||||
roles[rule.name]['delete'] |= rule.can_delete
|
||||
for perm in ['view', 'add', 'change', 'delete']:
|
||||
permissions[perm] = user.is_superuser or check_user_role(user, role, perm)
|
||||
|
||||
roles[role] = permissions
|
||||
|
||||
return {'roles': roles}
|
||||
|
@ -309,6 +309,11 @@ if DEBUG_TOOLBAR_ENABLED: # pragma: no cover
|
||||
INSTALLED_APPS.append('debug_toolbar')
|
||||
MIDDLEWARE.append('debug_toolbar.middleware.DebugToolbarMiddleware')
|
||||
|
||||
DEBUG_TOOLBAR_CONFIG = {
|
||||
'RESULTS_CACHE_SIZE': 100,
|
||||
'OBSERVE_REQUEST_CALLBACK': lambda x: False,
|
||||
}
|
||||
|
||||
# Internal IP addresses allowed to see the debug toolbar
|
||||
INTERNAL_IPS = [
|
||||
'127.0.0.1',
|
||||
|
10
InvenTree/InvenTree/static/bootstrap-table/bootstrap-table.min.css
vendored
Normal file
10
InvenTree/InvenTree/static/bootstrap-table/bootstrap-table.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
10
InvenTree/InvenTree/static/bootstrap-table/bootstrap-table.min.js
vendored
Normal file
10
InvenTree/InvenTree/static/bootstrap-table/bootstrap-table.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -470,8 +470,8 @@ main {
|
||||
}
|
||||
|
||||
.part-thumb {
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
width: 256px;
|
||||
height: 256px;
|
||||
margin: 2px;
|
||||
padding: 3px;
|
||||
object-fit: contain;
|
||||
|
@ -222,6 +222,29 @@
|
||||
};
|
||||
|
||||
var l10 = {
|
||||
code: 'bn',
|
||||
week: {
|
||||
dow: 0, // Sunday is the first day of the week.
|
||||
doy: 6, // The week that contains Jan 1st is the first week of the year.
|
||||
},
|
||||
buttonText: {
|
||||
prev: 'পেছনে',
|
||||
next: 'সামনে',
|
||||
today: 'আজ',
|
||||
month: 'মাস',
|
||||
week: 'সপ্তাহ',
|
||||
day: 'দিন',
|
||||
list: 'তালিকা',
|
||||
},
|
||||
weekText: 'সপ্তাহ',
|
||||
allDayText: 'সারাদিন',
|
||||
moreLinkText: function(n) {
|
||||
return '+অন্যান্য ' + n
|
||||
},
|
||||
noEventsText: 'কোনো ইভেন্ট নেই',
|
||||
};
|
||||
|
||||
var l11 = {
|
||||
code: 'bs',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -244,7 +267,7 @@
|
||||
noEventsText: 'Nema događaja za prikazivanje',
|
||||
};
|
||||
|
||||
var l11 = {
|
||||
var l12 = {
|
||||
code: 'ca',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -265,7 +288,7 @@
|
||||
noEventsText: 'No hi ha esdeveniments per mostrar',
|
||||
};
|
||||
|
||||
var l12 = {
|
||||
var l13 = {
|
||||
code: 'cs',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -288,7 +311,7 @@
|
||||
noEventsText: 'Žádné akce k zobrazení',
|
||||
};
|
||||
|
||||
var l13 = {
|
||||
var l14 = {
|
||||
code: 'cy',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -310,7 +333,7 @@
|
||||
noEventsText: 'Dim digwyddiadau',
|
||||
};
|
||||
|
||||
var l14 = {
|
||||
var l15 = {
|
||||
code: 'da',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -331,7 +354,12 @@
|
||||
noEventsText: 'Ingen arrangementer at vise',
|
||||
};
|
||||
|
||||
var l15 = {
|
||||
function affix$1(buttonText) {
|
||||
return (buttonText === 'Tag' || buttonText === 'Monat') ? 'r' :
|
||||
buttonText === 'Jahr' ? 's' : ''
|
||||
}
|
||||
|
||||
var l16 = {
|
||||
code: 'de-at',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -348,14 +376,49 @@
|
||||
list: 'Terminübersicht',
|
||||
},
|
||||
weekText: 'KW',
|
||||
weekTextLong: 'Woche',
|
||||
allDayText: 'Ganztägig',
|
||||
moreLinkText: function(n) {
|
||||
return '+ weitere ' + n
|
||||
},
|
||||
noEventsText: 'Keine Ereignisse anzuzeigen',
|
||||
buttonHints: {
|
||||
prev(buttonText) {
|
||||
return `Vorherige${affix$1(buttonText)} ${buttonText}`
|
||||
},
|
||||
next(buttonText) {
|
||||
return `Nächste${affix$1(buttonText)} ${buttonText}`
|
||||
},
|
||||
today(buttonText) {
|
||||
// → Heute, Diese Woche, Dieser Monat, Dieses Jahr
|
||||
if (buttonText === 'Tag') {
|
||||
return 'Heute'
|
||||
}
|
||||
return `Diese${affix$1(buttonText)} ${buttonText}`
|
||||
},
|
||||
},
|
||||
viewHint(buttonText) {
|
||||
// → Tagesansicht, Wochenansicht, Monatsansicht, Jahresansicht
|
||||
const glue = buttonText === 'Woche' ? 'n' : buttonText === 'Monat' ? 's' : 'es';
|
||||
return buttonText + glue + 'ansicht'
|
||||
},
|
||||
navLinkHint: 'Gehe zu $0',
|
||||
moreLinkHint(eventCnt) {
|
||||
return 'Zeige ' + (eventCnt === 1 ?
|
||||
'ein weiteres Ereignis' :
|
||||
eventCnt + ' weitere Ereignisse')
|
||||
},
|
||||
closeHint: 'Schließen',
|
||||
timeHint: 'Uhrzeit',
|
||||
eventHint: 'Ereignis',
|
||||
};
|
||||
|
||||
var l16 = {
|
||||
function affix(buttonText) {
|
||||
return (buttonText === 'Tag' || buttonText === 'Monat') ? 'r' :
|
||||
buttonText === 'Jahr' ? 's' : ''
|
||||
}
|
||||
|
||||
var l17 = {
|
||||
code: 'de',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -372,14 +435,44 @@
|
||||
list: 'Terminübersicht',
|
||||
},
|
||||
weekText: 'KW',
|
||||
weekTextLong: 'Woche',
|
||||
allDayText: 'Ganztägig',
|
||||
moreLinkText: function(n) {
|
||||
return '+ weitere ' + n
|
||||
},
|
||||
noEventsText: 'Keine Ereignisse anzuzeigen',
|
||||
buttonHints: {
|
||||
prev(buttonText) {
|
||||
return `Vorherige${affix(buttonText)} ${buttonText}`
|
||||
},
|
||||
next(buttonText) {
|
||||
return `Nächste${affix(buttonText)} ${buttonText}`
|
||||
},
|
||||
today(buttonText) {
|
||||
// → Heute, Diese Woche, Dieser Monat, Dieses Jahr
|
||||
if (buttonText === 'Tag') {
|
||||
return 'Heute'
|
||||
}
|
||||
return `Diese${affix(buttonText)} ${buttonText}`
|
||||
},
|
||||
},
|
||||
viewHint(buttonText) {
|
||||
// → Tagesansicht, Wochenansicht, Monatsansicht, Jahresansicht
|
||||
const glue = buttonText === 'Woche' ? 'n' : buttonText === 'Monat' ? 's' : 'es';
|
||||
return buttonText + glue + 'ansicht'
|
||||
},
|
||||
navLinkHint: 'Gehe zu $0',
|
||||
moreLinkHint(eventCnt) {
|
||||
return 'Zeige ' + (eventCnt === 1 ?
|
||||
'ein weiteres Ereignis' :
|
||||
eventCnt + ' weitere Ereignisse')
|
||||
},
|
||||
closeHint: 'Schließen',
|
||||
timeHint: 'Uhrzeit',
|
||||
eventHint: 'Ereignis',
|
||||
};
|
||||
|
||||
var l17 = {
|
||||
var l18 = {
|
||||
code: 'el',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -400,31 +493,61 @@
|
||||
noEventsText: 'Δεν υπάρχουν γεγονότα προς εμφάνιση',
|
||||
};
|
||||
|
||||
var l18 = {
|
||||
var l19 = {
|
||||
code: 'en-au',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
doy: 4, // The week that contains Jan 4th is the first week of the year.
|
||||
},
|
||||
buttonHints: {
|
||||
prev: 'Previous $0',
|
||||
next: 'Next $0',
|
||||
today: 'This $0',
|
||||
},
|
||||
viewHint: '$0 view',
|
||||
navLinkHint: 'Go to $0',
|
||||
moreLinkHint(eventCnt) {
|
||||
return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`
|
||||
},
|
||||
};
|
||||
|
||||
var l19 = {
|
||||
var l20 = {
|
||||
code: 'en-gb',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
doy: 4, // The week that contains Jan 4th is the first week of the year.
|
||||
},
|
||||
buttonHints: {
|
||||
prev: 'Previous $0',
|
||||
next: 'Next $0',
|
||||
today: 'This $0',
|
||||
},
|
||||
viewHint: '$0 view',
|
||||
navLinkHint: 'Go to $0',
|
||||
moreLinkHint(eventCnt) {
|
||||
return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`
|
||||
},
|
||||
};
|
||||
|
||||
var l20 = {
|
||||
var l21 = {
|
||||
code: 'en-nz',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
doy: 4, // The week that contains Jan 4th is the first week of the year.
|
||||
},
|
||||
buttonHints: {
|
||||
prev: 'Previous $0',
|
||||
next: 'Next $0',
|
||||
today: 'This $0',
|
||||
},
|
||||
viewHint: '$0 view',
|
||||
navLinkHint: 'Go to $0',
|
||||
moreLinkHint(eventCnt) {
|
||||
return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`
|
||||
},
|
||||
};
|
||||
|
||||
var l21 = {
|
||||
var l22 = {
|
||||
code: 'eo',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -445,7 +568,7 @@
|
||||
noEventsText: 'Neniuj eventoj por montri',
|
||||
};
|
||||
|
||||
var l22 = {
|
||||
var l23 = {
|
||||
code: 'es',
|
||||
week: {
|
||||
dow: 0, // Sunday is the first day of the week.
|
||||
@ -466,7 +589,7 @@
|
||||
noEventsText: 'No hay eventos para mostrar',
|
||||
};
|
||||
|
||||
var l23 = {
|
||||
var l24 = {
|
||||
code: 'es',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -481,13 +604,32 @@
|
||||
day: 'Día',
|
||||
list: 'Agenda',
|
||||
},
|
||||
buttonHints: {
|
||||
prev: '$0 antes',
|
||||
next: '$0 siguiente',
|
||||
today(buttonText) {
|
||||
return (buttonText === 'Día') ? 'Hoy' :
|
||||
((buttonText === 'Semana') ? 'Esta' : 'Este') + ' ' + buttonText.toLocaleLowerCase()
|
||||
},
|
||||
},
|
||||
viewHint(buttonText) {
|
||||
return 'Vista ' + (buttonText === 'Semana' ? 'de la' : 'del') + ' ' + buttonText.toLocaleLowerCase()
|
||||
},
|
||||
weekText: 'Sm',
|
||||
weekTextLong: 'Semana',
|
||||
allDayText: 'Todo el día',
|
||||
moreLinkText: 'más',
|
||||
moreLinkHint(eventCnt) {
|
||||
return `Mostrar ${eventCnt} eventos más`
|
||||
},
|
||||
noEventsText: 'No hay eventos para mostrar',
|
||||
navLinkHint: 'Ir al $0',
|
||||
closeHint: 'Cerrar',
|
||||
timeHint: 'La hora',
|
||||
eventHint: 'Evento',
|
||||
};
|
||||
|
||||
var l24 = {
|
||||
var l25 = {
|
||||
code: 'et',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -510,7 +652,7 @@
|
||||
noEventsText: 'Kuvamiseks puuduvad sündmused',
|
||||
};
|
||||
|
||||
var l25 = {
|
||||
var l26 = {
|
||||
code: 'eu',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -531,7 +673,7 @@
|
||||
noEventsText: 'Ez dago ekitaldirik erakusteko',
|
||||
};
|
||||
|
||||
var l26 = {
|
||||
var l27 = {
|
||||
code: 'fa',
|
||||
week: {
|
||||
dow: 6, // Saturday is the first day of the week.
|
||||
@ -555,7 +697,7 @@
|
||||
noEventsText: 'هیچ رویدادی به نمایش',
|
||||
};
|
||||
|
||||
var l27 = {
|
||||
var l28 = {
|
||||
code: 'fi',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -576,7 +718,7 @@
|
||||
noEventsText: 'Ei näytettäviä tapahtumia',
|
||||
};
|
||||
|
||||
var l28 = {
|
||||
var l29 = {
|
||||
code: 'fr',
|
||||
buttonText: {
|
||||
prev: 'Précédent',
|
||||
@ -594,7 +736,7 @@
|
||||
noEventsText: 'Aucun événement à afficher',
|
||||
};
|
||||
|
||||
var l29 = {
|
||||
var l30 = {
|
||||
code: 'fr-ch',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -616,7 +758,7 @@
|
||||
noEventsText: 'Aucun événement à afficher',
|
||||
};
|
||||
|
||||
var l30 = {
|
||||
var l31 = {
|
||||
code: 'fr',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -638,7 +780,7 @@
|
||||
noEventsText: 'Aucun événement à afficher',
|
||||
};
|
||||
|
||||
var l31 = {
|
||||
var l32 = {
|
||||
code: 'gl',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -659,7 +801,7 @@
|
||||
noEventsText: 'Non hai eventos para amosar',
|
||||
};
|
||||
|
||||
var l32 = {
|
||||
var l33 = {
|
||||
code: 'he',
|
||||
direction: 'rtl',
|
||||
buttonText: {
|
||||
@ -677,7 +819,7 @@
|
||||
weekText: 'שבוע',
|
||||
};
|
||||
|
||||
var l33 = {
|
||||
var l34 = {
|
||||
code: 'hi',
|
||||
week: {
|
||||
dow: 0, // Sunday is the first day of the week.
|
||||
@ -700,7 +842,7 @@
|
||||
noEventsText: 'कोई घटनाओं को प्रदर्शित करने के लिए',
|
||||
};
|
||||
|
||||
var l34 = {
|
||||
var l35 = {
|
||||
code: 'hr',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -723,7 +865,7 @@
|
||||
noEventsText: 'Nema događaja za prikaz',
|
||||
};
|
||||
|
||||
var l35 = {
|
||||
var l36 = {
|
||||
code: 'hu',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -736,7 +878,7 @@
|
||||
month: 'Hónap',
|
||||
week: 'Hét',
|
||||
day: 'Nap',
|
||||
list: 'Napló',
|
||||
list: 'Lista',
|
||||
},
|
||||
weekText: 'Hét',
|
||||
allDayText: 'Egész nap',
|
||||
@ -744,7 +886,7 @@
|
||||
noEventsText: 'Nincs megjeleníthető esemény',
|
||||
};
|
||||
|
||||
var l36 = {
|
||||
var l37 = {
|
||||
code: 'hy-am',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -767,7 +909,7 @@
|
||||
noEventsText: 'Բացակայում է իրադարձությունը ցուցադրելու',
|
||||
};
|
||||
|
||||
var l37 = {
|
||||
var l38 = {
|
||||
code: 'id',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -788,7 +930,7 @@
|
||||
noEventsText: 'Tidak ada acara untuk ditampilkan',
|
||||
};
|
||||
|
||||
var l38 = {
|
||||
var l39 = {
|
||||
code: 'is',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -809,7 +951,7 @@
|
||||
noEventsText: 'Engir viðburðir til að sýna',
|
||||
};
|
||||
|
||||
var l39 = {
|
||||
var l40 = {
|
||||
code: 'it',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -832,7 +974,7 @@
|
||||
noEventsText: 'Non ci sono eventi da visualizzare',
|
||||
};
|
||||
|
||||
var l40 = {
|
||||
var l41 = {
|
||||
code: 'ja',
|
||||
buttonText: {
|
||||
prev: '前',
|
||||
@ -851,7 +993,7 @@
|
||||
noEventsText: '表示する予定はありません',
|
||||
};
|
||||
|
||||
var l41 = {
|
||||
var l42 = {
|
||||
code: 'ka',
|
||||
week: {
|
||||
dow: 1,
|
||||
@ -874,7 +1016,7 @@
|
||||
noEventsText: 'ღონისძიებები არ არის',
|
||||
};
|
||||
|
||||
var l42 = {
|
||||
var l43 = {
|
||||
code: 'kk',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -897,7 +1039,29 @@
|
||||
noEventsText: 'Көрсету үшін оқиғалар жоқ',
|
||||
};
|
||||
|
||||
var l43 = {
|
||||
var l44 = {
|
||||
code: 'km',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
doy: 4, // The week that contains Jan 4th is the first week of the year.
|
||||
},
|
||||
buttonText: {
|
||||
prev: 'មុន',
|
||||
next: 'បន្ទាប់',
|
||||
today: 'ថ្ងៃនេះ',
|
||||
year: 'ឆ្នាំ',
|
||||
month: 'ខែ',
|
||||
week: 'សប្តាហ៍',
|
||||
day: 'ថ្ងៃ',
|
||||
list: 'បញ្ជី',
|
||||
},
|
||||
weekText: 'សប្តាហ៍',
|
||||
allDayText: 'ពេញមួយថ្ងៃ',
|
||||
moreLinkText: 'ច្រើនទៀត',
|
||||
noEventsText: 'គ្មានព្រឹត្តិការណ៍ត្រូវបង្ហាញ',
|
||||
};
|
||||
|
||||
var l45 = {
|
||||
code: 'ko',
|
||||
buttonText: {
|
||||
prev: '이전달',
|
||||
@ -914,7 +1078,29 @@
|
||||
noEventsText: '일정이 없습니다',
|
||||
};
|
||||
|
||||
var l44 = {
|
||||
var l46 = {
|
||||
code: 'ku',
|
||||
week: {
|
||||
dow: 6, // Saturday is the first day of the week.
|
||||
doy: 12, // The week that contains Jan 1st is the first week of the year.
|
||||
},
|
||||
direction: 'rtl',
|
||||
buttonText: {
|
||||
prev: 'پێشتر',
|
||||
next: 'دواتر',
|
||||
today: 'ئەمڕو',
|
||||
month: 'مانگ',
|
||||
week: 'هەفتە',
|
||||
day: 'ڕۆژ',
|
||||
list: 'بەرنامە',
|
||||
},
|
||||
weekText: 'هەفتە',
|
||||
allDayText: 'هەموو ڕۆژەکە',
|
||||
moreLinkText: 'زیاتر',
|
||||
noEventsText: 'هیچ ڕووداوێك نیە',
|
||||
};
|
||||
|
||||
var l47 = {
|
||||
code: 'lb',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -935,7 +1121,7 @@
|
||||
noEventsText: 'Nee Evenementer ze affichéieren',
|
||||
};
|
||||
|
||||
var l45 = {
|
||||
var l48 = {
|
||||
code: 'lt',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -956,7 +1142,7 @@
|
||||
noEventsText: 'Nėra įvykių rodyti',
|
||||
};
|
||||
|
||||
var l46 = {
|
||||
var l49 = {
|
||||
code: 'lv',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -979,7 +1165,7 @@
|
||||
noEventsText: 'Nav notikumu',
|
||||
};
|
||||
|
||||
var l47 = {
|
||||
var l50 = {
|
||||
code: 'mk',
|
||||
buttonText: {
|
||||
prev: 'претходно',
|
||||
@ -998,7 +1184,7 @@
|
||||
noEventsText: 'Нема настани за прикажување',
|
||||
};
|
||||
|
||||
var l48 = {
|
||||
var l51 = {
|
||||
code: 'ms',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1021,7 +1207,7 @@
|
||||
noEventsText: 'Tiada peristiwa untuk dipaparkan',
|
||||
};
|
||||
|
||||
var l49 = {
|
||||
var l52 = {
|
||||
code: 'nb',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1037,12 +1223,23 @@
|
||||
list: 'Agenda',
|
||||
},
|
||||
weekText: 'Uke',
|
||||
weekTextLong: 'Uke',
|
||||
allDayText: 'Hele dagen',
|
||||
moreLinkText: 'til',
|
||||
noEventsText: 'Ingen hendelser å vise',
|
||||
buttonHints: {
|
||||
prev: 'Forrige $0',
|
||||
next: 'Neste $0',
|
||||
today: 'Nåværende $0',
|
||||
},
|
||||
viewHint: '$0 visning',
|
||||
navLinkHint: 'Gå til $0',
|
||||
moreLinkHint(eventCnt) {
|
||||
return `Vis ${eventCnt} flere hendelse${eventCnt === 1 ? '' : 'r'}`
|
||||
},
|
||||
};
|
||||
|
||||
var l50 = {
|
||||
var l53 = {
|
||||
code: 'ne', // code for nepal
|
||||
week: {
|
||||
dow: 7, // Sunday is the first day of the week.
|
||||
@ -1063,7 +1260,7 @@
|
||||
noEventsText: 'देखाउनको लागि कुनै घटनाहरू छैनन्',
|
||||
};
|
||||
|
||||
var l51 = {
|
||||
var l54 = {
|
||||
code: 'nl',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1084,7 +1281,7 @@
|
||||
noEventsText: 'Geen evenementen om te laten zien',
|
||||
};
|
||||
|
||||
var l52 = {
|
||||
var l55 = {
|
||||
code: 'nn',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1105,7 +1302,7 @@
|
||||
noEventsText: 'Ingen hendelser å vise',
|
||||
};
|
||||
|
||||
var l53 = {
|
||||
var l56 = {
|
||||
code: 'pl',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1126,7 +1323,7 @@
|
||||
noEventsText: 'Brak wydarzeń do wyświetlenia',
|
||||
};
|
||||
|
||||
var l54 = {
|
||||
var l57 = {
|
||||
code: 'pt-br',
|
||||
buttonText: {
|
||||
prev: 'Anterior',
|
||||
@ -1145,7 +1342,7 @@
|
||||
noEventsText: 'Não há eventos para mostrar',
|
||||
};
|
||||
|
||||
var l55 = {
|
||||
var l58 = {
|
||||
code: 'pt',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1166,7 +1363,7 @@
|
||||
noEventsText: 'Não há eventos para mostrar',
|
||||
};
|
||||
|
||||
var l56 = {
|
||||
var l59 = {
|
||||
code: 'ro',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1189,7 +1386,7 @@
|
||||
noEventsText: 'Nu există evenimente de afișat',
|
||||
};
|
||||
|
||||
var l57 = {
|
||||
var l60 = {
|
||||
code: 'ru',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1212,7 +1409,28 @@
|
||||
noEventsText: 'Нет событий для отображения',
|
||||
};
|
||||
|
||||
var l58 = {
|
||||
var l61 = {
|
||||
code: 'si-lk',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
doy: 4, // The week that contains Jan 4th is the first week of the year.
|
||||
},
|
||||
buttonText: {
|
||||
prev: 'පෙර',
|
||||
next: 'පසු',
|
||||
today: 'අද',
|
||||
month: 'මාසය',
|
||||
week: 'සතිය',
|
||||
day: 'දවස',
|
||||
list: 'ලැයිස්තුව',
|
||||
},
|
||||
weekText: 'සති',
|
||||
allDayText: 'සියලු',
|
||||
moreLinkText: 'තවත්',
|
||||
noEventsText: 'මුකුත් නැත',
|
||||
};
|
||||
|
||||
var l62 = {
|
||||
code: 'sk',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1235,7 +1453,7 @@
|
||||
noEventsText: 'Žiadne akcie na zobrazenie',
|
||||
};
|
||||
|
||||
var l59 = {
|
||||
var l63 = {
|
||||
code: 'sl',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1256,7 +1474,24 @@
|
||||
noEventsText: 'Ni dogodkov za prikaz',
|
||||
};
|
||||
|
||||
var l60 = {
|
||||
var l64 = {
|
||||
code: 'sm',
|
||||
buttonText: {
|
||||
prev: 'Talu ai',
|
||||
next: 'Mulimuli atu',
|
||||
today: 'Aso nei',
|
||||
month: 'Masina',
|
||||
week: 'Vaiaso',
|
||||
day: 'Aso',
|
||||
list: 'Faasologa',
|
||||
},
|
||||
weekText: 'Vaiaso',
|
||||
allDayText: 'Aso atoa',
|
||||
moreLinkText: 'sili atu',
|
||||
noEventsText: 'Leai ni mea na tutupu',
|
||||
};
|
||||
|
||||
var l65 = {
|
||||
code: 'sq',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1279,7 +1514,7 @@
|
||||
noEventsText: 'Nuk ka evente për të shfaqur',
|
||||
};
|
||||
|
||||
var l61 = {
|
||||
var l66 = {
|
||||
code: 'sr-cyrl',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1302,7 +1537,7 @@
|
||||
noEventsText: 'Нема догађаја за приказ',
|
||||
};
|
||||
|
||||
var l62 = {
|
||||
var l67 = {
|
||||
code: 'sr',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1325,7 +1560,7 @@
|
||||
noEventsText: 'Nеma događaja za prikaz',
|
||||
};
|
||||
|
||||
var l63 = {
|
||||
var l68 = {
|
||||
code: 'sv',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1340,13 +1575,56 @@
|
||||
day: 'Dag',
|
||||
list: 'Program',
|
||||
},
|
||||
buttonHints: {
|
||||
prev(buttonText) {
|
||||
return `Föregående ${buttonText.toLocaleLowerCase()}`
|
||||
},
|
||||
next(buttonText) {
|
||||
return `Nästa ${buttonText.toLocaleLowerCase()}`
|
||||
},
|
||||
today(buttonText) {
|
||||
return (buttonText === 'Program' ? 'Detta' : 'Denna') + ' ' + buttonText.toLocaleLowerCase()
|
||||
},
|
||||
},
|
||||
viewHint: '$0 vy',
|
||||
navLinkHint: 'Gå till $0',
|
||||
moreLinkHint(eventCnt) {
|
||||
return `Visa ytterligare ${eventCnt} händelse${eventCnt === 1 ? '' : 'r'}`
|
||||
},
|
||||
weekText: 'v.',
|
||||
weekTextLong: 'Vecka',
|
||||
allDayText: 'Heldag',
|
||||
moreLinkText: 'till',
|
||||
noEventsText: 'Inga händelser att visa',
|
||||
closeHint: 'Stäng',
|
||||
timeHint: 'Klockan',
|
||||
eventHint: 'Händelse',
|
||||
};
|
||||
|
||||
var l64 = {
|
||||
var l69 = {
|
||||
code: 'ta-in',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
doy: 4, // The week that contains Jan 4th is the first week of the year.
|
||||
},
|
||||
buttonText: {
|
||||
prev: 'முந்தைய',
|
||||
next: 'அடுத்தது',
|
||||
today: 'இன்று',
|
||||
month: 'மாதம்',
|
||||
week: 'வாரம்',
|
||||
day: 'நாள்',
|
||||
list: 'தினசரி அட்டவணை',
|
||||
},
|
||||
weekText: 'வாரம்',
|
||||
allDayText: 'நாள் முழுவதும்',
|
||||
moreLinkText: function(n) {
|
||||
return '+ மேலும் ' + n
|
||||
},
|
||||
noEventsText: 'காண்பிக்க நிகழ்வுகள் இல்லை',
|
||||
};
|
||||
|
||||
var l70 = {
|
||||
code: 'th',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1370,7 +1648,7 @@
|
||||
noEventsText: 'ไม่มีกิจกรรมที่จะแสดง',
|
||||
};
|
||||
|
||||
var l65 = {
|
||||
var l71 = {
|
||||
code: 'tr',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1391,7 +1669,7 @@
|
||||
noEventsText: 'Gösterilecek etkinlik yok',
|
||||
};
|
||||
|
||||
var l66 = {
|
||||
var l72 = {
|
||||
code: 'ug',
|
||||
buttonText: {
|
||||
month: 'ئاي',
|
||||
@ -1402,7 +1680,7 @@
|
||||
allDayText: 'پۈتۈن كۈن',
|
||||
};
|
||||
|
||||
var l67 = {
|
||||
var l73 = {
|
||||
code: 'uk',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1425,7 +1703,7 @@
|
||||
noEventsText: 'Немає подій для відображення',
|
||||
};
|
||||
|
||||
var l68 = {
|
||||
var l74 = {
|
||||
code: 'uz',
|
||||
buttonText: {
|
||||
month: 'Oy',
|
||||
@ -1440,7 +1718,7 @@
|
||||
noEventsText: "Ko'rsatish uchun voqealar yo'q",
|
||||
};
|
||||
|
||||
var l69 = {
|
||||
var l75 = {
|
||||
code: 'vi',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
@ -1463,7 +1741,7 @@
|
||||
noEventsText: 'Không có sự kiện để hiển thị',
|
||||
};
|
||||
|
||||
var l70 = {
|
||||
var l76 = {
|
||||
code: 'zh-cn',
|
||||
week: {
|
||||
// GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效
|
||||
@ -1487,7 +1765,7 @@
|
||||
noEventsText: '没有事件显示',
|
||||
};
|
||||
|
||||
var l71 = {
|
||||
var l77 = {
|
||||
code: 'zh-tw',
|
||||
buttonText: {
|
||||
prev: '上月',
|
||||
@ -1507,7 +1785,7 @@
|
||||
/* eslint max-len: off */
|
||||
|
||||
var localesAll = [
|
||||
l0, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, l25, l26, l27, l28, l29, l30, l31, l32, l33, l34, l35, l36, l37, l38, l39, l40, l41, l42, l43, l44, l45, l46, l47, l48, l49, l50, l51, l52, l53, l54, l55, l56, l57, l58, l59, l60, l61, l62, l63, l64, l65, l66, l67, l68, l69, l70, l71,
|
||||
l0, l1, l2, l3, l4, l5, l6, l7, l8, l9, l10, l11, l12, l13, l14, l15, l16, l17, l18, l19, l20, l21, l22, l23, l24, l25, l26, l27, l28, l29, l30, l31, l32, l33, l34, l35, l36, l37, l38, l39, l40, l41, l42, l43, l44, l45, l46, l47, l48, l49, l50, l51, l52, l53, l54, l55, l56, l57, l58, l59, l60, l61, l62, l63, l64, l65, l66, l67, l68, l69, l70, l71, l72, l73, l74, l75, l76, l77,
|
||||
];
|
||||
|
||||
return localesAll;
|
||||
|
1
InvenTree/InvenTree/static/fullcalendar/locales-all.min.js
vendored
Normal file
1
InvenTree/InvenTree/static/fullcalendar/locales-all.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
29
InvenTree/InvenTree/static/fullcalendar/locales/bn.js
Normal file
29
InvenTree/InvenTree/static/fullcalendar/locales/bn.js
Normal file
@ -0,0 +1,29 @@
|
||||
FullCalendar.globalLocales.push(function () {
|
||||
'use strict';
|
||||
|
||||
var bn = {
|
||||
code: 'bn',
|
||||
week: {
|
||||
dow: 0, // Sunday is the first day of the week.
|
||||
doy: 6, // The week that contains Jan 1st is the first week of the year.
|
||||
},
|
||||
buttonText: {
|
||||
prev: 'পেছনে',
|
||||
next: 'সামনে',
|
||||
today: 'আজ',
|
||||
month: 'মাস',
|
||||
week: 'সপ্তাহ',
|
||||
day: 'দিন',
|
||||
list: 'তালিকা',
|
||||
},
|
||||
weekText: 'সপ্তাহ',
|
||||
allDayText: 'সারাদিন',
|
||||
moreLinkText: function(n) {
|
||||
return '+অন্যান্য ' + n
|
||||
},
|
||||
noEventsText: 'কোনো ইভেন্ট নেই',
|
||||
};
|
||||
|
||||
return bn;
|
||||
|
||||
}());
|
@ -1,6 +1,11 @@
|
||||
FullCalendar.globalLocales.push(function () {
|
||||
'use strict';
|
||||
|
||||
function affix(buttonText) {
|
||||
return (buttonText === 'Tag' || buttonText === 'Monat') ? 'r' :
|
||||
buttonText === 'Jahr' ? 's' : ''
|
||||
}
|
||||
|
||||
var deAt = {
|
||||
code: 'de-at',
|
||||
week: {
|
||||
@ -18,11 +23,41 @@ FullCalendar.globalLocales.push(function () {
|
||||
list: 'Terminübersicht',
|
||||
},
|
||||
weekText: 'KW',
|
||||
weekTextLong: 'Woche',
|
||||
allDayText: 'Ganztägig',
|
||||
moreLinkText: function(n) {
|
||||
return '+ weitere ' + n
|
||||
},
|
||||
noEventsText: 'Keine Ereignisse anzuzeigen',
|
||||
buttonHints: {
|
||||
prev(buttonText) {
|
||||
return `Vorherige${affix(buttonText)} ${buttonText}`
|
||||
},
|
||||
next(buttonText) {
|
||||
return `Nächste${affix(buttonText)} ${buttonText}`
|
||||
},
|
||||
today(buttonText) {
|
||||
// → Heute, Diese Woche, Dieser Monat, Dieses Jahr
|
||||
if (buttonText === 'Tag') {
|
||||
return 'Heute'
|
||||
}
|
||||
return `Diese${affix(buttonText)} ${buttonText}`
|
||||
},
|
||||
},
|
||||
viewHint(buttonText) {
|
||||
// → Tagesansicht, Wochenansicht, Monatsansicht, Jahresansicht
|
||||
const glue = buttonText === 'Woche' ? 'n' : buttonText === 'Monat' ? 's' : 'es';
|
||||
return buttonText + glue + 'ansicht'
|
||||
},
|
||||
navLinkHint: 'Gehe zu $0',
|
||||
moreLinkHint(eventCnt) {
|
||||
return 'Zeige ' + (eventCnt === 1 ?
|
||||
'ein weiteres Ereignis' :
|
||||
eventCnt + ' weitere Ereignisse')
|
||||
},
|
||||
closeHint: 'Schließen',
|
||||
timeHint: 'Uhrzeit',
|
||||
eventHint: 'Ereignis',
|
||||
};
|
||||
|
||||
return deAt;
|
||||
|
@ -1,6 +1,11 @@
|
||||
FullCalendar.globalLocales.push(function () {
|
||||
'use strict';
|
||||
|
||||
function affix(buttonText) {
|
||||
return (buttonText === 'Tag' || buttonText === 'Monat') ? 'r' :
|
||||
buttonText === 'Jahr' ? 's' : ''
|
||||
}
|
||||
|
||||
var de = {
|
||||
code: 'de',
|
||||
week: {
|
||||
@ -18,11 +23,41 @@ FullCalendar.globalLocales.push(function () {
|
||||
list: 'Terminübersicht',
|
||||
},
|
||||
weekText: 'KW',
|
||||
weekTextLong: 'Woche',
|
||||
allDayText: 'Ganztägig',
|
||||
moreLinkText: function(n) {
|
||||
return '+ weitere ' + n
|
||||
},
|
||||
noEventsText: 'Keine Ereignisse anzuzeigen',
|
||||
buttonHints: {
|
||||
prev(buttonText) {
|
||||
return `Vorherige${affix(buttonText)} ${buttonText}`
|
||||
},
|
||||
next(buttonText) {
|
||||
return `Nächste${affix(buttonText)} ${buttonText}`
|
||||
},
|
||||
today(buttonText) {
|
||||
// → Heute, Diese Woche, Dieser Monat, Dieses Jahr
|
||||
if (buttonText === 'Tag') {
|
||||
return 'Heute'
|
||||
}
|
||||
return `Diese${affix(buttonText)} ${buttonText}`
|
||||
},
|
||||
},
|
||||
viewHint(buttonText) {
|
||||
// → Tagesansicht, Wochenansicht, Monatsansicht, Jahresansicht
|
||||
const glue = buttonText === 'Woche' ? 'n' : buttonText === 'Monat' ? 's' : 'es';
|
||||
return buttonText + glue + 'ansicht'
|
||||
},
|
||||
navLinkHint: 'Gehe zu $0',
|
||||
moreLinkHint(eventCnt) {
|
||||
return 'Zeige ' + (eventCnt === 1 ?
|
||||
'ein weiteres Ereignis' :
|
||||
eventCnt + ' weitere Ereignisse')
|
||||
},
|
||||
closeHint: 'Schließen',
|
||||
timeHint: 'Uhrzeit',
|
||||
eventHint: 'Ereignis',
|
||||
};
|
||||
|
||||
return de;
|
||||
|
@ -7,6 +7,16 @@ FullCalendar.globalLocales.push(function () {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
doy: 4, // The week that contains Jan 4th is the first week of the year.
|
||||
},
|
||||
buttonHints: {
|
||||
prev: 'Previous $0',
|
||||
next: 'Next $0',
|
||||
today: 'This $0',
|
||||
},
|
||||
viewHint: '$0 view',
|
||||
navLinkHint: 'Go to $0',
|
||||
moreLinkHint(eventCnt) {
|
||||
return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`
|
||||
},
|
||||
};
|
||||
|
||||
return enAu;
|
||||
|
@ -7,6 +7,16 @@ FullCalendar.globalLocales.push(function () {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
doy: 4, // The week that contains Jan 4th is the first week of the year.
|
||||
},
|
||||
buttonHints: {
|
||||
prev: 'Previous $0',
|
||||
next: 'Next $0',
|
||||
today: 'This $0',
|
||||
},
|
||||
viewHint: '$0 view',
|
||||
navLinkHint: 'Go to $0',
|
||||
moreLinkHint(eventCnt) {
|
||||
return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`
|
||||
},
|
||||
};
|
||||
|
||||
return enGb;
|
||||
|
@ -7,6 +7,16 @@ FullCalendar.globalLocales.push(function () {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
doy: 4, // The week that contains Jan 4th is the first week of the year.
|
||||
},
|
||||
buttonHints: {
|
||||
prev: 'Previous $0',
|
||||
next: 'Next $0',
|
||||
today: 'This $0',
|
||||
},
|
||||
viewHint: '$0 view',
|
||||
navLinkHint: 'Go to $0',
|
||||
moreLinkHint(eventCnt) {
|
||||
return `Show ${eventCnt} more event${eventCnt === 1 ? '' : 's'}`
|
||||
},
|
||||
};
|
||||
|
||||
return enNz;
|
||||
|
@ -16,10 +16,29 @@ FullCalendar.globalLocales.push(function () {
|
||||
day: 'Día',
|
||||
list: 'Agenda',
|
||||
},
|
||||
buttonHints: {
|
||||
prev: '$0 antes',
|
||||
next: '$0 siguiente',
|
||||
today(buttonText) {
|
||||
return (buttonText === 'Día') ? 'Hoy' :
|
||||
((buttonText === 'Semana') ? 'Esta' : 'Este') + ' ' + buttonText.toLocaleLowerCase()
|
||||
},
|
||||
},
|
||||
viewHint(buttonText) {
|
||||
return 'Vista ' + (buttonText === 'Semana' ? 'de la' : 'del') + ' ' + buttonText.toLocaleLowerCase()
|
||||
},
|
||||
weekText: 'Sm',
|
||||
weekTextLong: 'Semana',
|
||||
allDayText: 'Todo el día',
|
||||
moreLinkText: 'más',
|
||||
moreLinkHint(eventCnt) {
|
||||
return `Mostrar ${eventCnt} eventos más`
|
||||
},
|
||||
noEventsText: 'No hay eventos para mostrar',
|
||||
navLinkHint: 'Ir al $0',
|
||||
closeHint: 'Cerrar',
|
||||
timeHint: 'La hora',
|
||||
eventHint: 'Evento',
|
||||
};
|
||||
|
||||
return es;
|
||||
|
@ -14,7 +14,7 @@ FullCalendar.globalLocales.push(function () {
|
||||
month: 'Hónap',
|
||||
week: 'Hét',
|
||||
day: 'Nap',
|
||||
list: 'Napló',
|
||||
list: 'Lista',
|
||||
},
|
||||
weekText: 'Hét',
|
||||
allDayText: 'Egész nap',
|
||||
|
28
InvenTree/InvenTree/static/fullcalendar/locales/km.js
Normal file
28
InvenTree/InvenTree/static/fullcalendar/locales/km.js
Normal file
@ -0,0 +1,28 @@
|
||||
FullCalendar.globalLocales.push(function () {
|
||||
'use strict';
|
||||
|
||||
var km = {
|
||||
code: 'km',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
doy: 4, // The week that contains Jan 4th is the first week of the year.
|
||||
},
|
||||
buttonText: {
|
||||
prev: 'មុន',
|
||||
next: 'បន្ទាប់',
|
||||
today: 'ថ្ងៃនេះ',
|
||||
year: 'ឆ្នាំ',
|
||||
month: 'ខែ',
|
||||
week: 'សប្តាហ៍',
|
||||
day: 'ថ្ងៃ',
|
||||
list: 'បញ្ជី',
|
||||
},
|
||||
weekText: 'សប្តាហ៍',
|
||||
allDayText: 'ពេញមួយថ្ងៃ',
|
||||
moreLinkText: 'ច្រើនទៀត',
|
||||
noEventsText: 'គ្មានព្រឹត្តិការណ៍ត្រូវបង្ហាញ',
|
||||
};
|
||||
|
||||
return km;
|
||||
|
||||
}());
|
28
InvenTree/InvenTree/static/fullcalendar/locales/ku.js
Normal file
28
InvenTree/InvenTree/static/fullcalendar/locales/ku.js
Normal file
@ -0,0 +1,28 @@
|
||||
FullCalendar.globalLocales.push(function () {
|
||||
'use strict';
|
||||
|
||||
var ku = {
|
||||
code: 'ku',
|
||||
week: {
|
||||
dow: 6, // Saturday is the first day of the week.
|
||||
doy: 12, // The week that contains Jan 1st is the first week of the year.
|
||||
},
|
||||
direction: 'rtl',
|
||||
buttonText: {
|
||||
prev: 'پێشتر',
|
||||
next: 'دواتر',
|
||||
today: 'ئەمڕو',
|
||||
month: 'مانگ',
|
||||
week: 'هەفتە',
|
||||
day: 'ڕۆژ',
|
||||
list: 'بەرنامە',
|
||||
},
|
||||
weekText: 'هەفتە',
|
||||
allDayText: 'هەموو ڕۆژەکە',
|
||||
moreLinkText: 'زیاتر',
|
||||
noEventsText: 'هیچ ڕووداوێك نیە',
|
||||
};
|
||||
|
||||
return ku;
|
||||
|
||||
}());
|
@ -17,9 +17,20 @@ FullCalendar.globalLocales.push(function () {
|
||||
list: 'Agenda',
|
||||
},
|
||||
weekText: 'Uke',
|
||||
weekTextLong: 'Uke',
|
||||
allDayText: 'Hele dagen',
|
||||
moreLinkText: 'til',
|
||||
noEventsText: 'Ingen hendelser å vise',
|
||||
buttonHints: {
|
||||
prev: 'Forrige $0',
|
||||
next: 'Neste $0',
|
||||
today: 'Nåværende $0',
|
||||
},
|
||||
viewHint: '$0 visning',
|
||||
navLinkHint: 'Gå til $0',
|
||||
moreLinkHint(eventCnt) {
|
||||
return `Vis ${eventCnt} flere hendelse${eventCnt === 1 ? '' : 'r'}`
|
||||
},
|
||||
};
|
||||
|
||||
return nb;
|
||||
|
27
InvenTree/InvenTree/static/fullcalendar/locales/si-lk.js
Normal file
27
InvenTree/InvenTree/static/fullcalendar/locales/si-lk.js
Normal file
@ -0,0 +1,27 @@
|
||||
FullCalendar.globalLocales.push(function () {
|
||||
'use strict';
|
||||
|
||||
var siLk = {
|
||||
code: 'si-lk',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
doy: 4, // The week that contains Jan 4th is the first week of the year.
|
||||
},
|
||||
buttonText: {
|
||||
prev: 'පෙර',
|
||||
next: 'පසු',
|
||||
today: 'අද',
|
||||
month: 'මාසය',
|
||||
week: 'සතිය',
|
||||
day: 'දවස',
|
||||
list: 'ලැයිස්තුව',
|
||||
},
|
||||
weekText: 'සති',
|
||||
allDayText: 'සියලු',
|
||||
moreLinkText: 'තවත්',
|
||||
noEventsText: 'මුකුත් නැත',
|
||||
};
|
||||
|
||||
return siLk;
|
||||
|
||||
}());
|
23
InvenTree/InvenTree/static/fullcalendar/locales/sm.js
Normal file
23
InvenTree/InvenTree/static/fullcalendar/locales/sm.js
Normal file
@ -0,0 +1,23 @@
|
||||
FullCalendar.globalLocales.push(function () {
|
||||
'use strict';
|
||||
|
||||
var sm = {
|
||||
code: 'sm',
|
||||
buttonText: {
|
||||
prev: 'Talu ai',
|
||||
next: 'Mulimuli atu',
|
||||
today: 'Aso nei',
|
||||
month: 'Masina',
|
||||
week: 'Vaiaso',
|
||||
day: 'Aso',
|
||||
list: 'Faasologa',
|
||||
},
|
||||
weekText: 'Vaiaso',
|
||||
allDayText: 'Aso atoa',
|
||||
moreLinkText: 'sili atu',
|
||||
noEventsText: 'Leai ni mea na tutupu',
|
||||
};
|
||||
|
||||
return sm;
|
||||
|
||||
}());
|
@ -16,10 +16,30 @@ FullCalendar.globalLocales.push(function () {
|
||||
day: 'Dag',
|
||||
list: 'Program',
|
||||
},
|
||||
buttonHints: {
|
||||
prev(buttonText) {
|
||||
return `Föregående ${buttonText.toLocaleLowerCase()}`
|
||||
},
|
||||
next(buttonText) {
|
||||
return `Nästa ${buttonText.toLocaleLowerCase()}`
|
||||
},
|
||||
today(buttonText) {
|
||||
return (buttonText === 'Program' ? 'Detta' : 'Denna') + ' ' + buttonText.toLocaleLowerCase()
|
||||
},
|
||||
},
|
||||
viewHint: '$0 vy',
|
||||
navLinkHint: 'Gå till $0',
|
||||
moreLinkHint(eventCnt) {
|
||||
return `Visa ytterligare ${eventCnt} händelse${eventCnt === 1 ? '' : 'r'}`
|
||||
},
|
||||
weekText: 'v.',
|
||||
weekTextLong: 'Vecka',
|
||||
allDayText: 'Heldag',
|
||||
moreLinkText: 'till',
|
||||
noEventsText: 'Inga händelser att visa',
|
||||
closeHint: 'Stäng',
|
||||
timeHint: 'Klockan',
|
||||
eventHint: 'Händelse',
|
||||
};
|
||||
|
||||
return sv;
|
||||
|
29
InvenTree/InvenTree/static/fullcalendar/locales/ta-in.js
Normal file
29
InvenTree/InvenTree/static/fullcalendar/locales/ta-in.js
Normal file
@ -0,0 +1,29 @@
|
||||
FullCalendar.globalLocales.push(function () {
|
||||
'use strict';
|
||||
|
||||
var taIn = {
|
||||
code: 'ta-in',
|
||||
week: {
|
||||
dow: 1, // Monday is the first day of the week.
|
||||
doy: 4, // The week that contains Jan 4th is the first week of the year.
|
||||
},
|
||||
buttonText: {
|
||||
prev: 'முந்தைய',
|
||||
next: 'அடுத்தது',
|
||||
today: 'இன்று',
|
||||
month: 'மாதம்',
|
||||
week: 'வாரம்',
|
||||
day: 'நாள்',
|
||||
list: 'தினசரி அட்டவணை',
|
||||
},
|
||||
weekText: 'வாரம்',
|
||||
allDayText: 'நாள் முழுவதும்',
|
||||
moreLinkText: function(n) {
|
||||
return '+ மேலும் ' + n
|
||||
},
|
||||
noEventsText: 'காண்பிக்க நிகழ்வுகள் இல்லை',
|
||||
};
|
||||
|
||||
return taIn;
|
||||
|
||||
}());
|
@ -1,11 +1,12 @@
|
||||
|
||||
/* classes attached to <body> */
|
||||
|
||||
/* TODO: make fc-event selector work when calender in shadow DOM */
|
||||
.fc-not-allowed,
|
||||
.fc-not-allowed .fc-event { /* override events' custom cursors */
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* TODO: not attached to body. attached to specific els. move */
|
||||
.fc-unselectable {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
@ -367,10 +368,6 @@ When it's NOT activated, the fc-button classes won't even be in the DOM.
|
||||
/* for most browsers, if a height isn't set on the table, can't do liquid-height within cells */
|
||||
/* serves as a min-height. harmless */
|
||||
}
|
||||
.fc .fc-scrollgrid-section-liquid {
|
||||
height: auto
|
||||
|
||||
}
|
||||
.fc .fc-scrollgrid-section-liquid > td {
|
||||
height: 100%; /* better than `auto`, for firefox */
|
||||
}
|
||||
@ -394,9 +391,8 @@ When it's NOT activated, the fc-button classes won't even be in the DOM.
|
||||
.fc .fc-scrollgrid-section-sticky > * {
|
||||
background: #fff;
|
||||
background: var(--fc-page-bg-color, #fff);
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
z-index: 2; /* TODO: var */
|
||||
z-index: 3; /* TODO: var */
|
||||
/* TODO: box-shadow when sticking */
|
||||
}
|
||||
.fc .fc-scrollgrid-section-header.fc-scrollgrid-section-sticky > * {
|
||||
@ -411,7 +407,6 @@ When it's NOT activated, the fc-button classes won't even be in the DOM.
|
||||
margin-bottom: -1px;
|
||||
}
|
||||
.fc-sticky { /* no .fc wrap because used as child of body */
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
}
|
||||
.fc .fc-view-harness {
|
||||
@ -535,14 +530,17 @@ a.fc-event:hover {
|
||||
bottom: -20px;
|
||||
}
|
||||
/* selecting (always TOUCH) */
|
||||
/* OR, focused by tab-index */
|
||||
/* (TODO: maybe not the best focus-styling for .fc-daygrid-dot-event) */
|
||||
/* ---------------------------------------------------------------------------------------------------- */
|
||||
.fc-event-selected {
|
||||
.fc-event-selected,
|
||||
.fc-event:focus {
|
||||
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.2)
|
||||
|
||||
/* expand hit area (subclasses should expand) */
|
||||
|
||||
}
|
||||
.fc-event-selected:before {
|
||||
.fc-event-selected:before, .fc-event:focus:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: 3;
|
||||
@ -551,12 +549,13 @@ a.fc-event:hover {
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
.fc-event-selected {
|
||||
.fc-event-selected,
|
||||
.fc-event:focus {
|
||||
|
||||
/* dimmer effect */
|
||||
|
||||
}
|
||||
.fc-event-selected:after {
|
||||
.fc-event-selected:after, .fc-event:focus:after {
|
||||
content: "";
|
||||
background: rgba(0, 0, 0, 0.25);
|
||||
background: var(--fc-event-selected-overlay-color, rgba(0, 0, 0, 0.25));
|
||||
@ -635,38 +634,33 @@ A HORIZONTAL event
|
||||
.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end {
|
||||
cursor: w-resize;
|
||||
left: -4px;
|
||||
left: calc(var(--fc-event-resizer-thickness, 8px) / -2);
|
||||
left: calc(-0.5 * var(--fc-event-resizer-thickness, 8px));
|
||||
}
|
||||
.fc-direction-ltr .fc-h-event:not(.fc-event-selected) .fc-event-resizer-end,
|
||||
.fc-direction-rtl .fc-h-event:not(.fc-event-selected) .fc-event-resizer-start {
|
||||
cursor: e-resize;
|
||||
right: -4px;
|
||||
right: calc(var(--fc-event-resizer-thickness, 8px) / -2);
|
||||
right: calc(-0.5 * var(--fc-event-resizer-thickness, 8px));
|
||||
}
|
||||
/* resizers for TOUCH */
|
||||
.fc-h-event.fc-event-selected .fc-event-resizer {
|
||||
top: 50%;
|
||||
margin-top: -4px;
|
||||
margin-top: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2);
|
||||
margin-top: calc(-0.5 * var(--fc-event-resizer-dot-total-width, 8px));
|
||||
}
|
||||
.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-start,
|
||||
.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-end {
|
||||
left: -4px;
|
||||
left: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2);
|
||||
left: calc(-0.5 * var(--fc-event-resizer-dot-total-width, 8px));
|
||||
}
|
||||
.fc-direction-ltr .fc-h-event.fc-event-selected .fc-event-resizer-end,
|
||||
.fc-direction-rtl .fc-h-event.fc-event-selected .fc-event-resizer-start {
|
||||
right: -4px;
|
||||
right: calc(var(--fc-event-resizer-dot-total-width, 8px) / -2);
|
||||
}
|
||||
|
||||
|
||||
:root {
|
||||
--fc-daygrid-event-dot-width: 8px;
|
||||
right: calc(-0.5 * var(--fc-event-resizer-dot-total-width, 8px));
|
||||
}
|
||||
.fc .fc-popover {
|
||||
position: fixed;
|
||||
top: 0; /* for when not positioned yet */
|
||||
position: absolute;
|
||||
z-index: 9999;
|
||||
box-shadow: 0 2px 6px rgba(0,0,0,.15);
|
||||
}
|
||||
.fc .fc-popover-header {
|
||||
@ -694,6 +688,11 @@ A HORIZONTAL event
|
||||
background: rgba(208, 208, 208, 0.3);
|
||||
background: var(--fc-neutral-bg-color, rgba(208, 208, 208, 0.3));
|
||||
}
|
||||
|
||||
|
||||
:root {
|
||||
--fc-daygrid-event-dot-width: 8px;
|
||||
}
|
||||
/* help things clear margins of inner content */
|
||||
.fc-daygrid-day-frame,
|
||||
.fc-daygrid-day-events,
|
||||
@ -814,8 +813,12 @@ A HORIZONTAL event
|
||||
}
|
||||
.fc .fc-daygrid-day-bottom {
|
||||
font-size: .85em;
|
||||
margin: 2px 3px 0;
|
||||
padding: 2px 3px 0
|
||||
}
|
||||
.fc .fc-daygrid-day-bottom:before {
|
||||
content: "";
|
||||
clear: both;
|
||||
display: table; }
|
||||
.fc .fc-daygrid-more-link {
|
||||
position: relative;
|
||||
z-index: 4;
|
||||
@ -843,9 +846,6 @@ A HORIZONTAL event
|
||||
/* popover */
|
||||
|
||||
}
|
||||
.fc .fc-more-popover {
|
||||
z-index: 8;
|
||||
}
|
||||
.fc .fc-more-popover .fc-popover-body {
|
||||
min-width: 220px;
|
||||
padding: 10px;
|
||||
@ -1139,14 +1139,14 @@ A VERTICAL event
|
||||
min-height: 100%; /* liquid-hack is below */
|
||||
position: relative;
|
||||
}
|
||||
.fc-liquid-hack .fc-timegrid-col-frame {
|
||||
.fc-media-screen.fc-liquid-hack .fc-timegrid-col-frame {
|
||||
height: auto;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
.fc-media-screen .fc-timegrid-cols {
|
||||
position: absolute; /* no z-index. children will decide and go above slots */
|
||||
top: 0;
|
||||
@ -1165,9 +1165,6 @@ A VERTICAL event
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.fc-media-screen .fc-timegrid-event-harness {
|
||||
position: absolute; /* top/left/right/bottom will all be set by JS */
|
||||
}
|
||||
.fc {
|
||||
|
||||
/* bg */
|
||||
@ -1211,18 +1208,30 @@ A VERTICAL event
|
||||
.fc-direction-rtl .fc-timegrid-col-events {
|
||||
margin: 0 2px 0 2.5%;
|
||||
}
|
||||
.fc-timegrid-event-harness {
|
||||
position: absolute /* top/left/right/bottom will all be set by JS */
|
||||
}
|
||||
.fc-timegrid-event-harness > .fc-timegrid-event {
|
||||
position: absolute; /* absolute WITHIN the harness */
|
||||
top: 0; /* for when not yet positioned */
|
||||
bottom: 0; /* " */
|
||||
left: 0;
|
||||
right: 0;
|
||||
}
|
||||
.fc-timegrid-event-harness-inset .fc-timegrid-event,
|
||||
.fc-timegrid-event.fc-event-mirror {
|
||||
.fc-timegrid-event.fc-event-mirror,
|
||||
.fc-timegrid-more-link {
|
||||
box-shadow: 0px 0px 0px 1px #fff;
|
||||
box-shadow: 0px 0px 0px 1px var(--fc-page-bg-color, #fff);
|
||||
}
|
||||
.fc-timegrid-event { /* events need to be root */
|
||||
|
||||
.fc-timegrid-event,
|
||||
.fc-timegrid-more-link { /* events need to be root */
|
||||
font-size: .85em;
|
||||
|
||||
font-size: var(--fc-small-font-size, .85em);
|
||||
border-radius: 3px
|
||||
|
||||
border-radius: 3px;
|
||||
}
|
||||
.fc-timegrid-event { /* events need to be root */
|
||||
margin-bottom: 1px /* give some space from bottom */
|
||||
}
|
||||
.fc-timegrid-event .fc-event-main {
|
||||
padding: 1px 1px 0;
|
||||
@ -1233,24 +1242,37 @@ A VERTICAL event
|
||||
font-size: var(--fc-small-font-size, .85em);
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
.fc-timegrid-event-condensed .fc-event-main-frame {
|
||||
.fc-timegrid-event-short .fc-event-main-frame {
|
||||
flex-direction: row;
|
||||
overflow: hidden;
|
||||
}
|
||||
.fc-timegrid-event-condensed .fc-event-time:after {
|
||||
.fc-timegrid-event-short .fc-event-time:after {
|
||||
content: '\00a0-\00a0'; /* dash surrounded by non-breaking spaces */
|
||||
}
|
||||
.fc-timegrid-event-condensed .fc-event-title {
|
||||
.fc-timegrid-event-short .fc-event-title {
|
||||
font-size: .85em;
|
||||
font-size: var(--fc-small-font-size, .85em)
|
||||
}
|
||||
.fc-media-screen .fc-timegrid-event {
|
||||
position: absolute; /* absolute WITHIN the harness */
|
||||
top: 0;
|
||||
bottom: 1px; /* stay away from bottom slot line */
|
||||
left: 0;
|
||||
.fc-timegrid-more-link { /* does NOT inherit from fc-timegrid-event */
|
||||
position: absolute;
|
||||
z-index: 9999; /* hack */
|
||||
color: inherit;
|
||||
color: var(--fc-more-link-text-color, inherit);
|
||||
background: #d0d0d0;
|
||||
background: var(--fc-more-link-bg-color, #d0d0d0);
|
||||
cursor: pointer;
|
||||
margin-bottom: 1px; /* match space below fc-timegrid-event */
|
||||
}
|
||||
.fc-timegrid-more-link-inner { /* has fc-sticky */
|
||||
padding: 3px 2px;
|
||||
top: 0;
|
||||
}
|
||||
.fc-direction-ltr .fc-timegrid-more-link {
|
||||
right: 0;
|
||||
}
|
||||
.fc-direction-rtl .fc-timegrid-more-link {
|
||||
left: 0;
|
||||
}
|
||||
.fc {
|
||||
|
||||
/* line */
|
||||
@ -1336,12 +1358,28 @@ A VERTICAL event
|
||||
border-right: 0;
|
||||
}
|
||||
.fc .fc-list-sticky .fc-list-day > * { /* the cells */
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
background: #fff;
|
||||
background: var(--fc-page-bg-color, #fff); /* for when headers are styled to be transparent and sticky */
|
||||
}
|
||||
.fc {
|
||||
|
||||
/* only exists for aria reasons, hide for non-screen-readers */
|
||||
|
||||
}
|
||||
.fc .fc-list-table thead {
|
||||
position: absolute;
|
||||
left: -10000px;
|
||||
}
|
||||
.fc {
|
||||
|
||||
/* the table's border-style:hidden gets confused by hidden thead. force-hide top border of first cell */
|
||||
|
||||
}
|
||||
.fc .fc-list-table tbody > tr:first-child th {
|
||||
border-top: 0;
|
||||
}
|
||||
.fc .fc-list-table th {
|
||||
padding: 0; /* uses an inner-wrapper instead... */
|
||||
}
|
||||
@ -1427,3 +1465,31 @@ A VERTICAL event
|
||||
color: inherit; /* natural color for navlinks */
|
||||
}
|
||||
|
||||
|
||||
|
||||
.fc-theme-bootstrap5 a:not([href]) {
|
||||
color: inherit;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
|
||||
.fc-theme-bootstrap5 .fc-list,
|
||||
.fc-theme-bootstrap5 .fc-scrollgrid,
|
||||
.fc-theme-bootstrap5 td,
|
||||
.fc-theme-bootstrap5 th {
|
||||
border: 1px solid var(--bs-gray-400);
|
||||
}
|
||||
|
||||
.fc-theme-bootstrap5 {
|
||||
|
||||
/* HACK: reapply core styles after highe-precedence border statement above */
|
||||
}
|
||||
|
||||
.fc-theme-bootstrap5 .fc-scrollgrid {
|
||||
border-right-width: 0;
|
||||
border-bottom-width: 0;
|
||||
}
|
||||
|
||||
.fc-theme-bootstrap5-shaded {
|
||||
background-color: var(--bs-gray-200);
|
||||
}
|
||||
|
||||
|
File diff suppressed because one or more lines are too long
1
InvenTree/InvenTree/static/fullcalendar/main.min.css
vendored
Normal file
1
InvenTree/InvenTree/static/fullcalendar/main.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
6
InvenTree/InvenTree/static/fullcalendar/main.min.js
vendored
Normal file
6
InvenTree/InvenTree/static/fullcalendar/main.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -107,14 +107,9 @@ function inventreeDocReady() {
|
||||
|
||||
// Callback to launch the 'About' window
|
||||
$('#launch-about').click(function() {
|
||||
var modal = $('#modal-about');
|
||||
|
||||
modal.modal({
|
||||
backdrop: 'static',
|
||||
keyboard: true,
|
||||
launchModalForm(`/about/`, {
|
||||
no_post: true,
|
||||
});
|
||||
|
||||
modal.modal('show');
|
||||
});
|
||||
|
||||
// Callback to launch the 'Database Stats' window
|
||||
@ -126,8 +121,6 @@ function inventreeDocReady() {
|
||||
|
||||
// Initialize clipboard-buttons
|
||||
attachClipboard('.clip-btn');
|
||||
attachClipboard('.clip-btn', 'modal-about');
|
||||
attachClipboard('.clip-btn-version', 'modal-about', 'about-copy-text');
|
||||
|
||||
// Generate brand-icons
|
||||
$('.brand-icon').each(function(i, obj) {
|
||||
|
@ -1,7 +1,6 @@
|
||||
"""Unit tests for the main web views."""
|
||||
|
||||
import os
|
||||
import re
|
||||
|
||||
from django.urls import reverse
|
||||
|
||||
@ -42,17 +41,3 @@ class ViewTests(InvenTreeTestCase):
|
||||
self.assertIn("<div id='detail-panels'>", content)
|
||||
|
||||
# TODO: In future, run the javascript and ensure that the panels get created!
|
||||
|
||||
def test_js_load(self):
|
||||
"""Test that the required javascript files are loaded correctly."""
|
||||
# Change this number as more javascript files are added to the index page
|
||||
N_SCRIPT_FILES = 40
|
||||
|
||||
content = self.get_index_page()
|
||||
|
||||
# Extract all required javascript files from the index page content
|
||||
script_files = re.findall("<script type='text\\/javascript' src=\"([^\"]*)\"><\\/script>", content)
|
||||
|
||||
self.assertEqual(len(script_files), N_SCRIPT_FILES)
|
||||
|
||||
# TODO: Request the javascript files from the server, and ensure they are correcty loaded
|
||||
|
@ -31,7 +31,7 @@ from stock.urls import stock_urls
|
||||
from users.api import user_urls
|
||||
|
||||
from .api import InfoView, NotFoundView
|
||||
from .views import (AppearanceSelectView, CurrencyRefreshView,
|
||||
from .views import (AboutView, AppearanceSelectView, CurrencyRefreshView,
|
||||
CustomConnectionsView, CustomEmailView,
|
||||
CustomPasswordResetFromKeyView,
|
||||
CustomSessionDeleteOtherView, CustomSessionDeleteView,
|
||||
@ -150,6 +150,7 @@ frontendpatterns = [
|
||||
re_path(r'^notifications/', include(notifications_urls)),
|
||||
re_path(r'^search/', SearchView.as_view(), name='search'),
|
||||
re_path(r'^settings/', include(settings_urls)),
|
||||
re_path(r'^about/', AboutView.as_view(), name='about'),
|
||||
re_path(r'^stats/', DatabaseStatsView.as_view(), name='stats'),
|
||||
|
||||
# admin sites
|
||||
|
@ -750,6 +750,13 @@ class DatabaseStatsView(AjaxView):
|
||||
ajax_form_title = _("System Information")
|
||||
|
||||
|
||||
class AboutView(AjaxView):
|
||||
"""A view for displaying InvenTree version information"""
|
||||
|
||||
ajax_template_name = "about.html"
|
||||
ajax_form_title = _("About InvenTree")
|
||||
|
||||
|
||||
class NotificationsView(TemplateView):
|
||||
"""View for showing notifications."""
|
||||
|
||||
|
@ -17,7 +17,7 @@
|
||||
{% block thumbnail %}
|
||||
<img class="part-thumb"
|
||||
{% if build.part.image %}
|
||||
src="{{ build.part.image.url }}"
|
||||
src="{{ build.part.image.preview.url }}"
|
||||
{% else %}
|
||||
src="{% static 'img/blank_image.png' %}"
|
||||
{% endif %}/>
|
||||
|
@ -127,7 +127,10 @@ class Company(models.Model):
|
||||
upload_to=rename_company_image,
|
||||
null=True,
|
||||
blank=True,
|
||||
variations={'thumbnail': (128, 128)},
|
||||
variations={
|
||||
'thumbnail': (128, 128),
|
||||
'preview': (256, 256),
|
||||
},
|
||||
delete_orphans=True,
|
||||
verbose_name=_('Image'),
|
||||
)
|
||||
|
@ -47,7 +47,7 @@
|
||||
<div class='dropzone part-thumb-container' id='company-thumb'>
|
||||
<img class="part-thumb" id='company-image'
|
||||
{% if company.image %}
|
||||
src="{{ company.image.url }}"
|
||||
src="{{ company.image.preview.url }}"
|
||||
{% else %}
|
||||
src="{% static 'img/blank_image.png' %}"
|
||||
{% endif %}/>
|
||||
|
@ -50,7 +50,7 @@
|
||||
{% block thumbnail %}
|
||||
<img class='part-thumb'
|
||||
{% if part.part.image %}
|
||||
src='{{ part.part.image.url }}'
|
||||
src='{{ part.part.image.preview.url }}'
|
||||
{% else %}
|
||||
src="{% static 'img/blank_image.png' %}"
|
||||
{% endif %}/>
|
||||
|
@ -62,7 +62,7 @@
|
||||
{% block thumbnail %}
|
||||
<img class='part-thumb'
|
||||
{% if part.part.image %}
|
||||
src='{{ part.part.image.url }}'
|
||||
src='{{ part.part.image.preview.url }}'
|
||||
{% else %}
|
||||
src="{% static 'img/blank_image.png' %}"
|
||||
{% endif %}/>
|
||||
|
@ -19,7 +19,7 @@ Relevant PRs:
|
||||
from decimal import Decimal
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import OuterRef, Q
|
||||
from django.db.models import F, FloatField, Func, OuterRef, Q, Subquery
|
||||
from django.db.models.functions import Coalesce
|
||||
|
||||
from sql_util.utils import SubquerySum
|
||||
@ -139,3 +139,22 @@ def variant_stock_query(reference: str = '', filter: Q = stock.models.StockItem.
|
||||
part__lft__gt=OuterRef(f'{reference}lft'),
|
||||
part__rght__lt=OuterRef(f'{reference}rght'),
|
||||
).filter(filter)
|
||||
|
||||
|
||||
def annotate_variant_quantity(subquery: Q, reference: str = 'quantity'):
|
||||
"""Create a subquery annotation for all variant part stock items on the given parent query
|
||||
|
||||
Args:
|
||||
subquery: A 'variant_stock_query' Q object
|
||||
reference: The relationship reference of the variant stock items from the current queryset
|
||||
"""
|
||||
|
||||
return Coalesce(
|
||||
Subquery(
|
||||
subquery.annotate(
|
||||
total=Func(F(reference), function='SUM', output_field=FloatField())
|
||||
).values('total')
|
||||
),
|
||||
0,
|
||||
output_field=FloatField(),
|
||||
)
|
||||
|
@ -13,7 +13,7 @@ from django.contrib.auth.models import User
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Q, Sum, UniqueConstraint
|
||||
from django.db.models import ExpressionWrapper, F, Q, Sum, UniqueConstraint
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.db.models.signals import post_save
|
||||
from django.db.utils import IntegrityError
|
||||
@ -34,6 +34,7 @@ from stdimage.models import StdImageField
|
||||
import common.models
|
||||
import InvenTree.ready
|
||||
import InvenTree.tasks
|
||||
import part.filters as part_filters
|
||||
import part.settings as part_settings
|
||||
from build import models as BuildModels
|
||||
from common.models import InvenTreeSetting
|
||||
@ -74,9 +75,9 @@ class PartCategory(MetadataMixin, InvenTreeTree):
|
||||
tree_id = self.tree_id
|
||||
|
||||
# Update each part in this category to point to the parent category
|
||||
for part in self.parts.all():
|
||||
part.category = self.parent
|
||||
part.save()
|
||||
for p in self.parts.all():
|
||||
p.category = self.parent
|
||||
p.save()
|
||||
|
||||
# Update each child category
|
||||
for child in self.children.all():
|
||||
@ -221,7 +222,7 @@ class PartCategory(MetadataMixin, InvenTreeTree):
|
||||
|
||||
if include_parents:
|
||||
queryset = PartCategoryStar.objects.filter(
|
||||
category__pk__in=[cat.pk for cat in cats]
|
||||
category__in=cats,
|
||||
)
|
||||
else:
|
||||
queryset = PartCategoryStar.objects.filter(
|
||||
@ -800,7 +801,10 @@ class Part(MetadataMixin, MPTTModel):
|
||||
upload_to=rename_part_image,
|
||||
null=True,
|
||||
blank=True,
|
||||
variations={'thumbnail': (128, 128)},
|
||||
variations={
|
||||
'thumbnail': (128, 128),
|
||||
'preview': (256, 256),
|
||||
},
|
||||
delete_orphans=False,
|
||||
verbose_name=_('Image'),
|
||||
)
|
||||
@ -968,13 +972,10 @@ class Part(MetadataMixin, MPTTModel):
|
||||
def requiring_build_orders(self):
|
||||
"""Return list of outstanding build orders which require this part."""
|
||||
# List parts that this part is required for
|
||||
parts = self.get_used_in().all()
|
||||
|
||||
part_ids = [part.pk for part in parts]
|
||||
|
||||
# Now, get a list of outstanding build orders which require this part
|
||||
builds = BuildModels.Build.objects.filter(
|
||||
part__in=part_ids,
|
||||
part__in=self.get_used_in().all(),
|
||||
status__in=BuildStatus.ACTIVE_CODES
|
||||
)
|
||||
|
||||
@ -1098,7 +1099,7 @@ class Part(MetadataMixin, MPTTModel):
|
||||
|
||||
if include_variants:
|
||||
queryset = queryset.filter(
|
||||
part__pk__in=[part.pk for part in self.get_ancestors(include_self=True)]
|
||||
part__in=self.get_ancestors(include_self=True),
|
||||
)
|
||||
else:
|
||||
queryset = queryset.filter(part=self)
|
||||
@ -1142,18 +1143,70 @@ class Part(MetadataMixin, MPTTModel):
|
||||
|
||||
total = None
|
||||
|
||||
bom_items = self.get_bom_items().prefetch_related('sub_part__stock_items')
|
||||
# Prefetch related tables, to reduce query expense
|
||||
queryset = self.get_bom_items().prefetch_related(
|
||||
'sub_part__stock_items',
|
||||
'sub_part__stock_items__allocations',
|
||||
'sub_part__stock_items__sales_order_allocations',
|
||||
'substitutes',
|
||||
'substitutes__part__stock_items',
|
||||
)
|
||||
|
||||
# Calculate the minimum number of parts that can be built using each sub-part
|
||||
for item in bom_items.all():
|
||||
stock = item.sub_part.available_stock
|
||||
# Annotate the 'available stock' for each part in the BOM
|
||||
ref = 'sub_part__'
|
||||
queryset = queryset.alias(
|
||||
total_stock=part_filters.annotate_total_stock(reference=ref),
|
||||
so_allocations=part_filters.annotate_sales_order_allocations(reference=ref),
|
||||
bo_allocations=part_filters.annotate_build_order_allocations(reference=ref),
|
||||
)
|
||||
|
||||
# If (by some chance) we get here but the BOM item quantity is invalid,
|
||||
# ignore!
|
||||
if item.quantity <= 0:
|
||||
continue
|
||||
# Calculate the 'available stock' based on previous annotations
|
||||
queryset = queryset.annotate(
|
||||
available_stock=ExpressionWrapper(
|
||||
F('total_stock') - F('so_allocations') - F('bo_allocations'),
|
||||
output_field=models.DecimalField(),
|
||||
)
|
||||
)
|
||||
|
||||
n = int(stock / item.quantity)
|
||||
# Extract similar information for any 'substitute' parts
|
||||
ref = 'substitutes__part__'
|
||||
queryset = queryset.alias(
|
||||
sub_total_stock=part_filters.annotate_total_stock(reference=ref),
|
||||
sub_so_allocations=part_filters.annotate_sales_order_allocations(reference=ref),
|
||||
sub_bo_allocations=part_filters.annotate_build_order_allocations(reference=ref),
|
||||
)
|
||||
|
||||
queryset = queryset.annotate(
|
||||
substitute_stock=ExpressionWrapper(
|
||||
F('sub_total_stock') - F('sub_so_allocations') - F('sub_bo_allocations'),
|
||||
output_field=models.DecimalField(),
|
||||
)
|
||||
)
|
||||
|
||||
# Extract similar information for any 'variant' parts
|
||||
variant_stock_query = part_filters.variant_stock_query(reference='sub_part__')
|
||||
|
||||
queryset = queryset.alias(
|
||||
var_total_stock=part_filters.annotate_variant_quantity(variant_stock_query, reference='quantity'),
|
||||
var_bo_allocations=part_filters.annotate_variant_quantity(variant_stock_query, reference='allocations__quantity'),
|
||||
var_so_allocations=part_filters.annotate_variant_quantity(variant_stock_query, reference='sales_order_allocations__quantity'),
|
||||
)
|
||||
|
||||
queryset = queryset.annotate(
|
||||
variant_stock=ExpressionWrapper(
|
||||
F('var_total_stock') - F('var_bo_allocations') - F('var_so_allocations'),
|
||||
output_field=models.DecimalField(),
|
||||
)
|
||||
)
|
||||
|
||||
for item in queryset.all():
|
||||
# Iterate through each item in the queryset, work out the limiting quantity
|
||||
quantity = item.available_stock + item.substitute_stock
|
||||
|
||||
if item.allow_variants:
|
||||
quantity += item.variant_stock
|
||||
|
||||
n = int(quantity / item.quantity)
|
||||
|
||||
if total is None or n < total:
|
||||
total = n
|
||||
@ -1336,11 +1389,10 @@ class Part(MetadataMixin, MPTTModel):
|
||||
parents = self.get_ancestors(include_self=False)
|
||||
|
||||
# There are parents available
|
||||
if parents.count() > 0:
|
||||
parent_ids = [p.pk for p in parents]
|
||||
if parents.exists():
|
||||
|
||||
parent_filter = Q(
|
||||
part__id__in=parent_ids,
|
||||
part__in=parents,
|
||||
inherited=True
|
||||
)
|
||||
|
||||
@ -1425,7 +1477,7 @@ class Part(MetadataMixin, MPTTModel):
|
||||
@property
|
||||
def has_bom(self):
|
||||
"""Return True if this Part instance has any BOM items"""
|
||||
return self.get_bom_items().count() > 0
|
||||
return self.get_bom_items().exists()
|
||||
|
||||
def get_trackable_parts(self):
|
||||
"""Return a queryset of all trackable parts in the BOM for this part."""
|
||||
@ -1440,7 +1492,7 @@ class Part(MetadataMixin, MPTTModel):
|
||||
|
||||
This is important when building the part.
|
||||
"""
|
||||
return self.get_trackable_parts().count() > 0
|
||||
return self.get_trackable_parts().exists()
|
||||
|
||||
@property
|
||||
def bom_count(self):
|
||||
@ -1482,7 +1534,7 @@ class Part(MetadataMixin, MPTTModel):
|
||||
# Validate each line item, ignoring inherited ones
|
||||
bom_items = self.get_bom_items(include_inherited=False)
|
||||
|
||||
for item in bom_items.all():
|
||||
for item in bom_items:
|
||||
item.validate_hash()
|
||||
|
||||
self.bom_checksum = self.get_bom_hash()
|
||||
@ -1509,7 +1561,7 @@ class Part(MetadataMixin, MPTTModel):
|
||||
if parts is None:
|
||||
parts = set()
|
||||
|
||||
bom_items = self.get_bom_items().all()
|
||||
bom_items = self.get_bom_items()
|
||||
|
||||
for bom_item in bom_items:
|
||||
|
||||
@ -1533,7 +1585,7 @@ class Part(MetadataMixin, MPTTModel):
|
||||
def has_complete_bom_pricing(self):
|
||||
"""Return true if there is pricing information for each item in the BOM."""
|
||||
use_internal = common.models.InvenTreeSetting.get_setting('PART_BOM_USE_INTERNAL_PRICE', False)
|
||||
for item in self.get_bom_items().all().select_related('sub_part'):
|
||||
for item in self.get_bom_items().select_related('sub_part'):
|
||||
if item.sub_part.get_price_range(internal=use_internal) is None:
|
||||
return False
|
||||
|
||||
@ -1609,7 +1661,7 @@ class Part(MetadataMixin, MPTTModel):
|
||||
min_price = None
|
||||
max_price = None
|
||||
|
||||
for item in self.get_bom_items().all().select_related('sub_part'):
|
||||
for item in self.get_bom_items().select_related('sub_part'):
|
||||
|
||||
if item.sub_part.pk == self.pk:
|
||||
logger.warning(f"WARNING: BomItem ID {item.pk} contains itself in BOM")
|
||||
@ -1689,7 +1741,7 @@ class Part(MetadataMixin, MPTTModel):
|
||||
@property
|
||||
def has_price_breaks(self):
|
||||
"""Return True if this part has sale price breaks"""
|
||||
return self.price_breaks.count() > 0
|
||||
return self.price_breaks.exists()
|
||||
|
||||
@property
|
||||
def price_breaks(self):
|
||||
@ -1725,7 +1777,7 @@ class Part(MetadataMixin, MPTTModel):
|
||||
@property
|
||||
def has_internal_price_breaks(self):
|
||||
"""Return True if this Part has internal pricing information"""
|
||||
return self.internal_price_breaks.count() > 0
|
||||
return self.internal_price_breaks.exists()
|
||||
|
||||
@property
|
||||
def internal_price_breaks(self):
|
||||
@ -1978,7 +2030,7 @@ class Part(MetadataMixin, MPTTModel):
|
||||
@property
|
||||
def has_variants(self):
|
||||
"""Check if this Part object has variants underneath it."""
|
||||
return self.get_all_variants().count() > 0
|
||||
return self.get_all_variants().exists()
|
||||
|
||||
def get_all_variants(self):
|
||||
"""Return all Part object which exist as a variant under this part."""
|
||||
@ -1993,7 +2045,7 @@ class Part(MetadataMixin, MPTTModel):
|
||||
b) It has non-virtual template parts above it
|
||||
c) It has non-virtual sibling variants
|
||||
"""
|
||||
return self.get_conversion_options().count() > 0
|
||||
return self.get_conversion_options().exists()
|
||||
|
||||
def get_conversion_options(self):
|
||||
"""Return options for converting this part to a "variant" within the same tree.
|
||||
@ -2520,7 +2572,7 @@ class BomItem(DataImportMixin, models.Model):
|
||||
- Allow stock from all directly specified substitute parts
|
||||
- If allow_variants is True, allow all part variants
|
||||
"""
|
||||
return Q(part__in=[part.pk for part in self.get_valid_parts_for_allocation()])
|
||||
return Q(part__in=self.get_valid_parts_for_allocation())
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
"""Enforce 'clean' operation when saving a BomItem instance"""
|
||||
|
@ -4,8 +4,7 @@ import imghdr
|
||||
from decimal import Decimal
|
||||
|
||||
from django.db import models, transaction
|
||||
from django.db.models import (ExpressionWrapper, F, FloatField, Func, Q,
|
||||
Subquery)
|
||||
from django.db.models import ExpressionWrapper, F, FloatField, Q
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.urls import reverse_lazy
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
@ -251,8 +250,6 @@ class PartBriefSerializer(InvenTreeModelSerializer):
|
||||
|
||||
thumbnail = serializers.CharField(source='get_thumbnail_url', read_only=True)
|
||||
|
||||
stock = serializers.FloatField(source='total_stock')
|
||||
|
||||
class Meta:
|
||||
"""Metaclass defining serializer fields"""
|
||||
model = Part
|
||||
@ -270,7 +267,6 @@ class PartBriefSerializer(InvenTreeModelSerializer):
|
||||
'is_template',
|
||||
'purchaseable',
|
||||
'salable',
|
||||
'stock',
|
||||
'trackable',
|
||||
'virtual',
|
||||
'units',
|
||||
@ -322,14 +318,7 @@ class PartSerializer(InvenTreeModelSerializer):
|
||||
variant_query = part.filters.variant_stock_query()
|
||||
|
||||
queryset = queryset.annotate(
|
||||
variant_stock=Coalesce(
|
||||
Subquery(
|
||||
variant_query.annotate(
|
||||
total=Func(F('quantity'), function='SUM', output_field=FloatField())
|
||||
).values('total')),
|
||||
0,
|
||||
output_field=FloatField(),
|
||||
)
|
||||
variant_stock=part.filters.annotate_variant_quantity(variant_query, reference='quantity'),
|
||||
)
|
||||
|
||||
# Filter to limit builds to "active"
|
||||
@ -642,35 +631,14 @@ class BomItemSerializer(InvenTreeModelSerializer):
|
||||
variant_stock_query = part.filters.variant_stock_query(reference='sub_part__')
|
||||
|
||||
queryset = queryset.alias(
|
||||
variant_stock_total=Coalesce(
|
||||
Subquery(
|
||||
variant_stock_query.annotate(
|
||||
total=Func(F('quantity'), function='SUM', output_field=FloatField())
|
||||
).values('total')),
|
||||
0,
|
||||
output_field=FloatField()
|
||||
),
|
||||
variant_stock_build_order_allocations=Coalesce(
|
||||
Subquery(
|
||||
variant_stock_query.annotate(
|
||||
total=Func(F('sales_order_allocations__quantity'), function='SUM', output_field=FloatField()),
|
||||
).values('total')),
|
||||
0,
|
||||
output_field=FloatField(),
|
||||
),
|
||||
variant_stock_sales_order_allocations=Coalesce(
|
||||
Subquery(
|
||||
variant_stock_query.annotate(
|
||||
total=Func(F('allocations__quantity'), function='SUM', output_field=FloatField()),
|
||||
).values('total')),
|
||||
0,
|
||||
output_field=FloatField(),
|
||||
)
|
||||
variant_stock_total=part.filters.annotate_variant_quantity(variant_stock_query, reference='quantity'),
|
||||
variant_bo_allocations=part.filters.annotate_variant_quantity(variant_stock_query, reference='sales_order_allocations__quantity'),
|
||||
variant_so_allocations=part.filters.annotate_variant_quantity(variant_stock_query, reference='allocations__quantity'),
|
||||
)
|
||||
|
||||
queryset = queryset.annotate(
|
||||
available_variant_stock=ExpressionWrapper(
|
||||
F('variant_stock_total') - F('variant_stock_build_order_allocations') - F('variant_stock_sales_order_allocations'),
|
||||
F('variant_stock_total') - F('variant_bo_allocations') - F('variant_so_allocations'),
|
||||
output_field=FloatField(),
|
||||
)
|
||||
)
|
||||
|
@ -690,17 +690,6 @@
|
||||
});
|
||||
});
|
||||
|
||||
// Load the BOM table data in the pricing view
|
||||
{% if part.has_bom and roles.sales_order.view %}
|
||||
loadBomTable($("#bom-pricing-table"), {
|
||||
editable: false,
|
||||
bom_url: "{% url 'api-bom-list' %}",
|
||||
part_url: "{% url 'api-part-list' %}",
|
||||
parent_id: {{ part.id }} ,
|
||||
sub_part_detail: true,
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
onPanelLoad("purchase-orders", function() {
|
||||
loadPartPurchaseOrderTable(
|
||||
"#purchase-order-table",
|
||||
@ -885,152 +874,164 @@
|
||||
);
|
||||
});
|
||||
|
||||
onPanelLoad('pricing', function() {
|
||||
{% default_currency as currency %}
|
||||
|
||||
{% default_currency as currency %}
|
||||
// Load the BOM table data in the pricing view
|
||||
{% if part.has_bom and roles.sales_order.view %}
|
||||
loadBomTable($("#bom-pricing-table"), {
|
||||
editable: false,
|
||||
bom_url: "{% url 'api-bom-list' %}",
|
||||
part_url: "{% url 'api-part-list' %}",
|
||||
parent_id: {{ part.id }} ,
|
||||
sub_part_detail: true,
|
||||
});
|
||||
{% endif %}
|
||||
|
||||
// history graphs
|
||||
{% if price_history %}
|
||||
var purchasepricedata = {
|
||||
labels: [
|
||||
{% for line in price_history %}'{% render_date line.date %}',{% endfor %}
|
||||
],
|
||||
datasets: [{
|
||||
label: '{% blocktrans %}Purchase Unit Price - {{currency}}{% endblocktrans %}',
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||
borderColor: 'rgb(255, 99, 132)',
|
||||
yAxisID: 'y',
|
||||
data: [
|
||||
{% for line in price_history %}{{ line.price|stringformat:".2f" }},{% endfor %}
|
||||
],
|
||||
borderWidth: 1,
|
||||
type: 'line'
|
||||
},
|
||||
{% if 'price_diff' in price_history.0 %}
|
||||
{
|
||||
label: '{% blocktrans %}Unit Price-Cost Difference - {{currency}}{% endblocktrans %}',
|
||||
backgroundColor: 'rgba(68, 157, 68, 0.2)',
|
||||
borderColor: 'rgb(68, 157, 68)',
|
||||
yAxisID: 'y2',
|
||||
data: [
|
||||
{% for line in price_history %}{{ line.price_diff|stringformat:".2f" }},{% endfor %}
|
||||
],
|
||||
borderWidth: 1,
|
||||
type: 'line',
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
label: '{% blocktrans %}Supplier Unit Cost - {{currency}}{% endblocktrans %}',
|
||||
backgroundColor: 'rgba(70, 127, 155, 0.2)',
|
||||
borderColor: 'rgb(70, 127, 155)',
|
||||
yAxisID: 'y',
|
||||
data: [
|
||||
{% for line in price_history %}{{ line.price_part|stringformat:".2f" }},{% endfor %}
|
||||
],
|
||||
borderWidth: 1,
|
||||
type: 'line',
|
||||
hidden: true,
|
||||
},
|
||||
{% endif %}
|
||||
{
|
||||
label: '{% trans "Quantity" %}',
|
||||
backgroundColor: 'rgba(255, 206, 86, 0.2)',
|
||||
borderColor: 'rgb(255, 206, 86)',
|
||||
yAxisID: 'y1',
|
||||
data: [
|
||||
{% for line in price_history %}{{ line.qty|stringformat:"f" }},{% endfor %}
|
||||
],
|
||||
borderWidth: 1
|
||||
}]
|
||||
}
|
||||
var StockPriceChart = loadStockPricingChart($('#StockPriceChart'), purchasepricedata)
|
||||
{% endif %}
|
||||
|
||||
{% if bom_parts %}
|
||||
var bom_colors = randomColor({hue: 'green', count: {{ bom_parts|length }} })
|
||||
var bomdata = {
|
||||
labels: [{% for line in bom_parts %}'{{ line.name }}',{% endfor %}],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Price',
|
||||
data: [{% for line in bom_parts %}{{ line.min_price }},{% endfor %}],
|
||||
backgroundColor: bom_colors,
|
||||
},
|
||||
{% if bom_pie_max %}
|
||||
{
|
||||
label: 'Max Price',
|
||||
data: [{% for line in bom_parts %}{{ line.max_price }},{% endfor %}],
|
||||
backgroundColor: bom_colors,
|
||||
},
|
||||
{% endif %}
|
||||
]
|
||||
};
|
||||
var BomChart = loadBomChart(document.getElementById('BomChart'), bomdata)
|
||||
{% endif %}
|
||||
|
||||
|
||||
// Internal pricebreaks
|
||||
{% settings_value "PART_INTERNAL_PRICE" as show_internal_price %}
|
||||
{% if show_internal_price and roles.sales_order.view %}
|
||||
initPriceBreakSet(
|
||||
$('#internal-price-break-table'),
|
||||
{
|
||||
part_id: {{part.id}},
|
||||
pb_human_name: 'internal price break',
|
||||
pb_url_slug: 'internal-price',
|
||||
pb_url: '{% url 'api-part-internal-price-list' %}',
|
||||
pb_new_btn: $('#new-internal-price-break'),
|
||||
pb_new_url: '{% url 'api-part-internal-price-list' %}',
|
||||
linkedGraph: $('#InternalPriceBreakChart'),
|
||||
},
|
||||
);
|
||||
{% endif %}
|
||||
|
||||
// Sales pricebreaks
|
||||
{% if part.salable and roles.sales_order.view %}
|
||||
initPriceBreakSet(
|
||||
$('#price-break-table'),
|
||||
{
|
||||
part_id: {{part.id}},
|
||||
pb_human_name: 'sale price break',
|
||||
pb_url_slug: 'sale-price',
|
||||
pb_url: "{% url 'api-part-sale-price-list' %}",
|
||||
pb_new_btn: $('#new-price-break'),
|
||||
pb_new_url: '{% url 'api-part-sale-price-list' %}',
|
||||
linkedGraph: $('#SalePriceBreakChart'),
|
||||
},
|
||||
);
|
||||
{% endif %}
|
||||
|
||||
// Sale price history
|
||||
{% if sale_history %}
|
||||
var salepricedata = {
|
||||
// history graphs
|
||||
{% if price_history %}
|
||||
var purchasepricedata = {
|
||||
labels: [
|
||||
{% for line in sale_history %}'{% render_date line.date %}',{% endfor %}
|
||||
{% for line in price_history %}'{% render_date line.date %}',{% endfor %}
|
||||
],
|
||||
datasets: [{
|
||||
label: '{% blocktrans %}Unit Price - {{currency}}{% endblocktrans %}',
|
||||
label: '{% blocktrans %}Purchase Unit Price - {{currency}}{% endblocktrans %}',
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||
borderColor: 'rgb(255, 99, 132)',
|
||||
yAxisID: 'y',
|
||||
data: [
|
||||
{% for line in sale_history %}{{ line.price|stringformat:".2f" }},{% endfor %}
|
||||
{% for line in price_history %}{{ line.price|stringformat:".2f" }},{% endfor %}
|
||||
],
|
||||
borderWidth: 1,
|
||||
type: 'line'
|
||||
},
|
||||
{% if 'price_diff' in price_history.0 %}
|
||||
{
|
||||
label: '{% blocktrans %}Unit Price-Cost Difference - {{currency}}{% endblocktrans %}',
|
||||
backgroundColor: 'rgba(68, 157, 68, 0.2)',
|
||||
borderColor: 'rgb(68, 157, 68)',
|
||||
yAxisID: 'y2',
|
||||
data: [
|
||||
{% for line in price_history %}{{ line.price_diff|stringformat:".2f" }},{% endfor %}
|
||||
],
|
||||
borderWidth: 1,
|
||||
type: 'line',
|
||||
hidden: true,
|
||||
},
|
||||
{
|
||||
label: '{% blocktrans %}Supplier Unit Cost - {{currency}}{% endblocktrans %}',
|
||||
backgroundColor: 'rgba(70, 127, 155, 0.2)',
|
||||
borderColor: 'rgb(70, 127, 155)',
|
||||
yAxisID: 'y',
|
||||
data: [
|
||||
{% for line in price_history %}{{ line.price_part|stringformat:".2f" }},{% endfor %}
|
||||
],
|
||||
borderWidth: 1,
|
||||
type: 'line',
|
||||
hidden: true,
|
||||
},
|
||||
{% endif %}
|
||||
{
|
||||
label: '{% trans "Quantity" %}',
|
||||
backgroundColor: 'rgba(255, 206, 86, 0.2)',
|
||||
borderColor: 'rgb(255, 206, 86)',
|
||||
yAxisID: 'y1',
|
||||
data: [
|
||||
{% for line in sale_history %}{{ line.qty|stringformat:"f" }},{% endfor %}
|
||||
{% for line in price_history %}{{ line.qty|stringformat:"f" }},{% endfor %}
|
||||
],
|
||||
borderWidth: 1,
|
||||
type: 'bar',
|
||||
borderWidth: 1
|
||||
}]
|
||||
}
|
||||
var SalePriceChart = loadSellPricingChart($('#SalePriceChart'), salepricedata)
|
||||
{% endif %}
|
||||
var StockPriceChart = loadStockPricingChart($('#StockPriceChart'), purchasepricedata)
|
||||
{% endif %}
|
||||
|
||||
{% if bom_parts %}
|
||||
var bom_colors = randomColor({hue: 'green', count: {{ bom_parts|length }} })
|
||||
var bomdata = {
|
||||
labels: [{% for line in bom_parts %}'{{ line.name }}',{% endfor %}],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Price',
|
||||
data: [{% for line in bom_parts %}{{ line.min_price }},{% endfor %}],
|
||||
backgroundColor: bom_colors,
|
||||
},
|
||||
{% if bom_pie_max %}
|
||||
{
|
||||
label: 'Max Price',
|
||||
data: [{% for line in bom_parts %}{{ line.max_price }},{% endfor %}],
|
||||
backgroundColor: bom_colors,
|
||||
},
|
||||
{% endif %}
|
||||
]
|
||||
};
|
||||
var BomChart = loadBomChart(document.getElementById('BomChart'), bomdata)
|
||||
{% endif %}
|
||||
|
||||
|
||||
// Internal pricebreaks
|
||||
{% settings_value "PART_INTERNAL_PRICE" as show_internal_price %}
|
||||
{% if show_internal_price and roles.sales_order.view %}
|
||||
initPriceBreakSet(
|
||||
$('#internal-price-break-table'),
|
||||
{
|
||||
part_id: {{part.id}},
|
||||
pb_human_name: 'internal price break',
|
||||
pb_url_slug: 'internal-price',
|
||||
pb_url: '{% url 'api-part-internal-price-list' %}',
|
||||
pb_new_btn: $('#new-internal-price-break'),
|
||||
pb_new_url: '{% url 'api-part-internal-price-list' %}',
|
||||
linkedGraph: $('#InternalPriceBreakChart'),
|
||||
},
|
||||
);
|
||||
{% endif %}
|
||||
|
||||
// Sales pricebreaks
|
||||
{% if part.salable and roles.sales_order.view %}
|
||||
initPriceBreakSet(
|
||||
$('#price-break-table'),
|
||||
{
|
||||
part_id: {{part.id}},
|
||||
pb_human_name: 'sale price break',
|
||||
pb_url_slug: 'sale-price',
|
||||
pb_url: "{% url 'api-part-sale-price-list' %}",
|
||||
pb_new_btn: $('#new-price-break'),
|
||||
pb_new_url: '{% url 'api-part-sale-price-list' %}',
|
||||
linkedGraph: $('#SalePriceBreakChart'),
|
||||
},
|
||||
);
|
||||
{% endif %}
|
||||
|
||||
// Sale price history
|
||||
{% if sale_history %}
|
||||
var salepricedata = {
|
||||
labels: [
|
||||
{% for line in sale_history %}'{% render_date line.date %}',{% endfor %}
|
||||
],
|
||||
datasets: [{
|
||||
label: '{% blocktrans %}Unit Price - {{currency}}{% endblocktrans %}',
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.2)',
|
||||
borderColor: 'rgb(255, 99, 132)',
|
||||
yAxisID: 'y',
|
||||
data: [
|
||||
{% for line in sale_history %}{{ line.price|stringformat:".2f" }},{% endfor %}
|
||||
],
|
||||
borderWidth: 1,
|
||||
},
|
||||
{
|
||||
label: '{% trans "Quantity" %}',
|
||||
backgroundColor: 'rgba(255, 206, 86, 0.2)',
|
||||
borderColor: 'rgb(255, 206, 86)',
|
||||
yAxisID: 'y1',
|
||||
data: [
|
||||
{% for line in sale_history %}{{ line.qty|stringformat:"f" }},{% endfor %}
|
||||
],
|
||||
borderWidth: 1,
|
||||
type: 'bar',
|
||||
}]
|
||||
}
|
||||
var SalePriceChart = loadSellPricingChart($('#SalePriceChart'), salepricedata)
|
||||
{% endif %}
|
||||
});
|
||||
|
||||
enableSidebar('part');
|
||||
|
||||
|
@ -18,7 +18,7 @@
|
||||
{% endif %}
|
||||
<img class="part-thumb" id='part-image'
|
||||
{% if part.image %}
|
||||
src="{{ part.image.url }}"
|
||||
src="{{ part.image.preview.url }}"
|
||||
{% else %}
|
||||
src="{% static 'img/blank_image.png' %}"
|
||||
{% endif %}/>
|
||||
|
@ -74,7 +74,6 @@ class StockDetail(RetrieveUpdateDestroyAPI):
|
||||
kwargs['part_detail'] = True
|
||||
kwargs['location_detail'] = True
|
||||
kwargs['supplier_part_detail'] = True
|
||||
kwargs['test_detail'] = True
|
||||
kwargs['context'] = self.get_serializer_context()
|
||||
|
||||
return self.serializer_class(*args, **kwargs)
|
||||
|
@ -88,6 +88,12 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
@staticmethod
|
||||
def annotate_queryset(queryset):
|
||||
"""Add some extra annotations to the queryset, performing database queries as efficiently as possible."""
|
||||
|
||||
queryset = queryset.prefetch_related(
|
||||
'sales_order',
|
||||
'purchase_order',
|
||||
)
|
||||
|
||||
# Annotate the queryset with the total allocated to sales orders
|
||||
queryset = queryset.annotate(
|
||||
allocated=Coalesce(
|
||||
@ -136,20 +142,14 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
|
||||
location_detail = LocationBriefSerializer(source='location', many=False, read_only=True)
|
||||
|
||||
tracking_items = serializers.IntegerField(source='tracking_info_count', read_only=True, required=False)
|
||||
|
||||
quantity = InvenTreeDecimalField()
|
||||
|
||||
allocated = serializers.FloatField(source='allocation_count', required=False)
|
||||
|
||||
# Annotated fields
|
||||
tracking_items = serializers.IntegerField(read_only=True, required=False)
|
||||
allocated = serializers.FloatField(required=False)
|
||||
expired = serializers.BooleanField(required=False, read_only=True)
|
||||
|
||||
stale = serializers.BooleanField(required=False, read_only=True)
|
||||
|
||||
# serial = serializers.CharField(required=False)
|
||||
|
||||
required_tests = serializers.IntegerField(source='required_test_count', read_only=True, required=False)
|
||||
|
||||
purchase_price = InvenTree.serializers.InvenTreeMoneySerializer(
|
||||
label=_('Purchase Price'),
|
||||
max_digits=19, decimal_places=4,
|
||||
@ -171,7 +171,6 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
return str(obj.purchase_price) if obj.purchase_price else '-'
|
||||
|
||||
purchase_order_reference = serializers.CharField(source='purchase_order.reference', read_only=True)
|
||||
|
||||
sales_order_reference = serializers.CharField(source='sales_order.reference', read_only=True)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -179,7 +178,6 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
part_detail = kwargs.pop('part_detail', False)
|
||||
location_detail = kwargs.pop('location_detail', False)
|
||||
supplier_part_detail = kwargs.pop('supplier_part_detail', False)
|
||||
test_detail = kwargs.pop('test_detail', False)
|
||||
|
||||
super(StockItemSerializer, self).__init__(*args, **kwargs)
|
||||
|
||||
@ -192,9 +190,6 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
if supplier_part_detail is not True:
|
||||
self.fields.pop('supplier_part_detail')
|
||||
|
||||
if test_detail is not True:
|
||||
self.fields.pop('required_tests')
|
||||
|
||||
class Meta:
|
||||
"""Metaclass options."""
|
||||
|
||||
@ -208,7 +203,6 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
'delete_on_deplete',
|
||||
'expired',
|
||||
'expiry_date',
|
||||
'in_stock',
|
||||
'is_building',
|
||||
'link',
|
||||
'location',
|
||||
@ -222,7 +216,6 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
'purchase_order_reference',
|
||||
'pk',
|
||||
'quantity',
|
||||
'required_tests',
|
||||
'sales_order',
|
||||
'sales_order_reference',
|
||||
'serial',
|
||||
@ -249,7 +242,6 @@ class StockItemSerializer(InvenTree.serializers.InvenTreeModelSerializer):
|
||||
'stocktake_date',
|
||||
'stocktake_user',
|
||||
'updated',
|
||||
'in_stock'
|
||||
]
|
||||
|
||||
|
||||
|
@ -136,7 +136,7 @@
|
||||
{% endblock actions %}
|
||||
|
||||
{% block thumbnail %}
|
||||
<img class='part-thumb' {% if item.part.image %}src="{{ item.part.image.url }}"{% else %}src="{% static 'img/blank_image.png' %}"{% endif %}/>
|
||||
<img class='part-thumb' {% if item.part.image %}src="{{ item.part.image.preview.url }}"{% else %}src="{% static 'img/blank_image.png' %}"{% endif %}/>
|
||||
{% endblock thumbnail %}
|
||||
|
||||
{% block details %}
|
||||
|
@ -2,108 +2,87 @@
|
||||
{% load inventree_extras %}
|
||||
{% load i18n %}
|
||||
|
||||
<div class='modal fade modal-fixed-footer' tabindex='-1' role='dialog' id='modal-about'>
|
||||
<div class='modal-dialog'>
|
||||
<div class='modal-content'>
|
||||
<div class="modal-header">
|
||||
<img src="{% static 'img/inventree.png' %}" height='40px' style='float: left; padding-right: 25px;' alt='Inventree Logo'>
|
||||
<h4>{% trans "InvenTree Version Information" %}</h4>
|
||||
<button type='button' class='btn-close' data-bs-dismiss='modal' aria-label='{% trans "Close" %}'></button>
|
||||
</div>
|
||||
<div class='modal-form-content-wrapper'>
|
||||
<div class='modal-form-content'>
|
||||
<div>
|
||||
<table class='table table-striped table-condensed'>
|
||||
<col width='25'>
|
||||
<tr>
|
||||
<td><span class='fas fa-hashtag'></span></td>
|
||||
<td>{% trans "InvenTree Version" %}</td>
|
||||
<td>
|
||||
<a href="https://github.com/inventree/InvenTree/releases">{% inventree_version %}</a>{% include "clip.html" %}
|
||||
{% inventree_is_development as dev %}
|
||||
{% if dev %}
|
||||
<span class='badge badge-right rounded-pill bg-primary'>{% trans "Development Version" %}</span>
|
||||
{% else %}
|
||||
{% if up_to_date %}
|
||||
<span class='badge badge-right rounded-pill bg-success'>{% trans "Up to Date" %}</span>
|
||||
{% else %}
|
||||
<span class='badge badge-right rounded-pill bg-info'>{% trans "Update Available" %}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if dev %}
|
||||
{% inventree_commit_hash as hash %}
|
||||
{% if hash %}
|
||||
<tr>
|
||||
<td><span class='fas fa-code-branch'></span></td>
|
||||
<td>{% trans "Commit Hash" %}</td><td>{{ hash }}{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% inventree_commit_date as commit_date %}
|
||||
{% if commit_date %}
|
||||
<tr>
|
||||
<td><span class='fas fa-calendar-alt'></span></td>
|
||||
<td>{% trans "Commit Date" %}</td><td>{% render_date commit_date %}{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td><span class='fas fa-book'></span></td>
|
||||
<td>{% trans "InvenTree Documentation" %}</td>
|
||||
<td><a href="{% inventree_docs_url %}">{% inventree_docs_url %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-code'></span></td>
|
||||
<td>{% trans "API Version" %}</td>
|
||||
<td>{% inventree_api_version %}{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-hashtag'></span></td>
|
||||
<td>{% trans "Python Version" %}</td>
|
||||
<td>{% python_version %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-hashtag'></span></td>
|
||||
<td>{% trans "Django Version" %}</td>
|
||||
<td><a href="https://www.djangoproject.com/">{% django_version %}</a>{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fab fa-github'></span></td>
|
||||
<td>{% trans "View Code on GitHub" %}</td>
|
||||
<td><a href="{% inventree_github_url %}">{% inventree_github_url %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-balance-scale'></span></td>
|
||||
<td>{% trans "Credits" %}</td>
|
||||
<td><a href="{% inventree_credits_url %}">{% inventree_credits_url %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-mobile-alt'></span></td>
|
||||
<td>{% trans "Mobile App" %}</td>
|
||||
<td><a href="{% inventree_docs_url %}/app/app">{% inventree_docs_url %}/app/app</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-bug'></span></td>
|
||||
<td>{% trans "Submit Bug Report" %}</td>
|
||||
<td><a href='{% inventree_github_url %}/issues'>{% inventree_github_url %}issues</a></td>
|
||||
</tr>
|
||||
<tr><td></td><td></td>
|
||||
<td>
|
||||
<span style="display: none;" id="about-copy-text">{% include "version.html" %}</span>
|
||||
<span class="float-right">
|
||||
<button class="btn clip-btn-version" type="button" data-bs-toggle='tooltip' title='{% trans "copy to clipboard" %}'><em class="fas fa-copy"></em> {% trans "copy version information" %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class='modal-footer'>
|
||||
<button type='button' class='btn btn-outline-secondary' data-bs-dismiss='modal'>{% trans "Close" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<table class='table table-striped table-condensed'>
|
||||
<col width='25'>
|
||||
<tr>
|
||||
<td><span class='fas fa-hashtag'></span></td>
|
||||
<td>{% trans "InvenTree Version" %}</td>
|
||||
<td>
|
||||
<a href="https://github.com/inventree/InvenTree/releases">{% inventree_version %}</a>{% include "clip.html" %}
|
||||
{% inventree_is_development as dev %}
|
||||
{% if dev %}
|
||||
<span class='badge badge-right rounded-pill bg-primary'>{% trans "Development Version" %}</span>
|
||||
{% else %}
|
||||
{% if up_to_date %}
|
||||
<span class='badge badge-right rounded-pill bg-success'>{% trans "Up to Date" %}</span>
|
||||
{% else %}
|
||||
<span class='badge badge-right rounded-pill bg-info'>{% trans "Update Available" %}</span>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if dev %}
|
||||
{% inventree_commit_hash as hash %}
|
||||
{% if hash %}
|
||||
<tr>
|
||||
<td><span class='fas fa-code-branch'></span></td>
|
||||
<td>{% trans "Commit Hash" %}</td><td>{{ hash }}{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% inventree_commit_date as commit_date %}
|
||||
{% if commit_date %}
|
||||
<tr>
|
||||
<td><span class='fas fa-calendar-alt'></span></td>
|
||||
<td>{% trans "Commit Date" %}</td><td>{% render_date commit_date %}{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td><span class='fas fa-book'></span></td>
|
||||
<td>{% trans "InvenTree Documentation" %}</td>
|
||||
<td><a href="{% inventree_docs_url %}">{% inventree_docs_url %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-code'></span></td>
|
||||
<td>{% trans "API Version" %}</td>
|
||||
<td>{% inventree_api_version %}{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-hashtag'></span></td>
|
||||
<td>{% trans "Python Version" %}</td>
|
||||
<td>{% python_version %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-hashtag'></span></td>
|
||||
<td>{% trans "Django Version" %}</td>
|
||||
<td><a href="https://www.djangoproject.com/">{% django_version %}</a>{% include "clip.html" %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fab fa-github'></span></td>
|
||||
<td>{% trans "View Code on GitHub" %}</td>
|
||||
<td><a href="{% inventree_github_url %}">{% inventree_github_url %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-balance-scale'></span></td>
|
||||
<td>{% trans "Credits" %}</td>
|
||||
<td><a href="{% inventree_credits_url %}">{% inventree_credits_url %}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-mobile-alt'></span></td>
|
||||
<td>{% trans "Mobile App" %}</td>
|
||||
<td><a href="{% inventree_docs_url %}/app/app">{% inventree_docs_url %}/app/app</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><span class='fas fa-bug'></span></td>
|
||||
<td>{% trans "Submit Bug Report" %}</td>
|
||||
<td><a href='{% inventree_github_url %}/issues'>{% inventree_github_url %}issues</a></td>
|
||||
</tr>
|
||||
<tr><td></td><td></td>
|
||||
<td>
|
||||
<span style="display: none;" id="about-copy-text">{% include "version.html" %}</span>
|
||||
<span class="float-right">
|
||||
<button class="btn clip-btn-version" type="button" data-bs-toggle='tooltip' title='{% trans "copy to clipboard" %}'><em class="fas fa-copy"></em> {% trans "copy version information" %}</button>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
@ -84,29 +84,14 @@
|
||||
{% endblock %}
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script type="text/javascript" src="{% static 'script/jquery_3.3.1_jquery.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/jquery-ui/jquery-ui.min.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'bootstrap/js/bootstrap.bundle.min.js' %}"></script>
|
||||
|
||||
|
||||
<!-- general JS -->
|
||||
{% include "third_party_js.html" %}
|
||||
|
||||
<script type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'notification.js' %}"></script>
|
||||
|
||||
<!-- fontawesome -->
|
||||
<script type='text/javascript' src="{% static 'fontawesome/js/solid.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'fontawesome/js/brands.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'fontawesome/js/fontawesome.js' %}"></script>
|
||||
|
||||
<!-- 3rd party general js -->
|
||||
<script type="text/javascript" src="{% static 'fullcalendar/main.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'fullcalendar/locales-all.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'select2/js/select2.full.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/moment.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/chart.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/clipboard.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/randomColor.min.js' %}"></script>
|
||||
|
||||
|
||||
<script type='text/javascript'>
|
||||
|
||||
|
@ -39,15 +39,15 @@
|
||||
|
||||
<!-- CSS -->
|
||||
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'bootstrap-table/bootstrap-table.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'bootstrap-table/extensions/group-by-v2/bootstrap-table-group-by.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'bootstrap-table/bootstrap-table.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'bootstrap-table/extensions/group-by-v2/bootstrap-table-group-by.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'bootstrap-table/extensions/filter-control/bootstrap-table-filter-control.css' %}">
|
||||
<link rel='stylesheet' href='{% static "treegrid/css/jquery.treegrid.css" %}'>
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/brands.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fontawesome/css/solid.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'select2/css/select2.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'select2/css/select2.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'select2/css/select2-bootstrap-5-theme.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fullcalendar/main.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'fullcalendar/main.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'script/jquery-ui/jquery-ui.min.css' %}">
|
||||
<link rel="stylesheet" href="{% static 'easymde/easymde.min.css' %}">
|
||||
|
||||
@ -132,83 +132,48 @@
|
||||
</div>
|
||||
|
||||
{% include 'modals.html' %}
|
||||
{% if show_about %}{% include 'about.html' %}{% endif %}
|
||||
{% include "notifications.html" %}
|
||||
{% include "search.html" %}
|
||||
</div>
|
||||
|
||||
<!-- Scripts -->
|
||||
<script type="text/javascript" src="{% static 'script/jquery_3.3.1_jquery.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/jquery.form.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/jquery-ui/jquery-ui.min.js' %}"></script>
|
||||
{% include "third_party_js.html" %}
|
||||
|
||||
<script type="text/javascript" src="{% static 'bootstrap/js/bootstrap.bundle.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/bootstrap/bootstrap-treeview.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'bootstrap-table/bootstrap-table.js' %}"></script>
|
||||
|
||||
<!-- jquery-treegrid -->
|
||||
<script type='text/javascript' src='{% static "treegrid/js/jquery.treegrid.js" %}'></script>
|
||||
<script type='text/javascript' src='{% static "treegrid/js/jquery.treegrid.bootstrap3.js" %}'></script>
|
||||
|
||||
<!-- boostrap-table extensions -->
|
||||
<script type='text/javascript' src='{% static "bootstrap-table/extensions/group-by-v2/bootstrap-table-group-by.js" %}'></script>
|
||||
<script type='text/javascript' src='{% static "bootstrap-table/extensions/filter-control/bootstrap-table-filter-control.js" %}'></script>
|
||||
<script type='text/javascript' src='{% static "bootstrap-table/extensions/treegrid/bootstrap-table-treegrid.js" %}'></script>
|
||||
<script type='text/javascript' src='{% static "bootstrap-table/extensions/custom-view/bootstrap-table-custom-view.js" %}'></script>
|
||||
|
||||
<!-- 3rd party general js -->
|
||||
<script type="text/javascript" src="{% static 'fullcalendar/main.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'fullcalendar/locales-all.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'select2/js/select2.full.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/chart.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/moment.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/chartjs-adapter-moment.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'easymde/easymde.min.js' %}"></script>
|
||||
|
||||
<script type='text/javascript' src="{% static 'script/clipboard.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/randomColor.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/qr-scanner.umd.min.js' %}"></script>
|
||||
|
||||
<!-- general JS -->
|
||||
<script type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script>
|
||||
|
||||
<!-- dynamic javascript templates -->
|
||||
<script type='text/javascript' src="{% url 'calendar.js' %}"></script>
|
||||
<script type='text/javascript' src="{% url 'nav.js' %}"></script>
|
||||
<script type='text/javascript' src="{% url 'settings.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% url 'calendar.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% url 'nav.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% url 'settings.js' %}"></script>
|
||||
|
||||
<!-- translated javascript templates-->
|
||||
<script type='text/javascript' src="{% i18n_static 'api.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'attachment.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'barcode.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'bom.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'build.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'company.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'filters.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'forms.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'helpers.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'label.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'modals.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'model_renderers.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'order.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'part.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'report.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'search.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'stock.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'plugin.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'tables.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'table_filters.js' %}"></script>
|
||||
<script type='text/javascript' src="{% i18n_static 'notification.js' %}"></script>
|
||||
|
||||
<script type='text/javascript' src="{% static 'fontawesome/js/solid.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'fontawesome/js/regular.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'fontawesome/js/brands.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'fontawesome/js/fontawesome.min.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'api.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'attachment.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'barcode.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'bom.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'build.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'company.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'filters.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'forms.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'helpers.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'label.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'modals.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'model_renderers.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'order.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'part.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'report.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'search.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'stock.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'plugin.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'tables.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'table_filters.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% i18n_static 'notification.js' %}"></script>
|
||||
|
||||
{% block js_load %}
|
||||
{% endblock %}
|
||||
|
||||
<script type='text/javascript'>
|
||||
<script defer type='text/javascript'>
|
||||
|
||||
$(document).ready(function () {
|
||||
|
||||
|
@ -2540,7 +2540,7 @@ function loadBuildTable(table, options) {
|
||||
if (value) {
|
||||
return row.responsible_detail.name;
|
||||
} else {
|
||||
return '{% trans "No information" %}';
|
||||
return '-';
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -32,7 +32,7 @@
|
||||
<div class='card'>
|
||||
{% block details_left %}
|
||||
<div class='row'>
|
||||
<div class='col' style='max-width: 220px;'>
|
||||
<div class='col' style='max-width: 280px;'>
|
||||
{% block thumbnail %}
|
||||
{% endblock thumbnail %}
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
{% load static %}
|
||||
{% load i18n %}
|
||||
{% load inventree_extras %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
@ -65,15 +66,7 @@
|
||||
{% block body_scripts_general %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
<!-- 3rd party general js -->
|
||||
<script type="text/javascript" src="{% static 'fullcalendar/main.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'fullcalendar/locales-all.js' %}"></script>
|
||||
<script type="text/javascript" src="{% static 'select2/js/select2.full.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/moment.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/chart.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/clipboard.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/randomColor.min.js' %}"></script>
|
||||
{% include "third_party_js.html" %}
|
||||
|
||||
<!-- general JS -->
|
||||
<script type='text/javascript' src="{% static 'script/inventree/inventree.js' %}"></script>
|
||||
|
37
InvenTree/templates/third_party_js.html
Normal file
37
InvenTree/templates/third_party_js.html
Normal file
@ -0,0 +1,37 @@
|
||||
{% load static %}
|
||||
|
||||
<!-- jquery -->
|
||||
<script type="text/javascript" src="{% static 'script/jquery_3.3.1_jquery.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/jquery.form.min.js' %}"></script>
|
||||
<script type='text/javascript' src="{% static 'script/jquery-ui/jquery-ui.min.js' %}"></script>
|
||||
|
||||
<!-- Bootstrap-->
|
||||
<script type="text/javascript" src="{% static 'bootstrap/js/bootstrap.bundle.min.js' %}"></script>
|
||||
|
||||
<!-- Bootstrap Table -->
|
||||
<script defer type='text/javascript' src="{% static 'script/bootstrap/bootstrap-treeview.js' %}"></script>
|
||||
<script defer type='text/javascript' src='{% static "treegrid/js/jquery.treegrid.js" %}'></script>
|
||||
<script defer type='text/javascript' src='{% static "treegrid/js/jquery.treegrid.bootstrap3.js" %}'></script>
|
||||
<script defer type='text/javascript' src="{% static 'bootstrap-table/bootstrap-table.min.js' %}"></script>
|
||||
<script defer type='text/javascript' src='{% static "bootstrap-table/extensions/group-by-v2/bootstrap-table-group-by.min.js" %}'></script>
|
||||
<script defer type='text/javascript' src='{% static "bootstrap-table/extensions/filter-control/bootstrap-table-filter-control.min.js" %}'></script>
|
||||
<script defer type='text/javascript' src='{% static "bootstrap-table/extensions/treegrid/bootstrap-table-treegrid.min.js" %}'></script>
|
||||
<script defer type='text/javascript' src='{% static "bootstrap-table/extensions/custom-view/bootstrap-table-custom-view.min.js" %}'></script>
|
||||
|
||||
<!-- fontawesome -->
|
||||
<script defer type='text/javascript' src="{% static 'fontawesome/js/solid.min.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% static 'fontawesome/js/regular.min.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% static 'fontawesome/js/brands.min.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% static 'fontawesome/js/fontawesome.min.js' %}"></script>
|
||||
|
||||
<!-- 3rd party general js -->
|
||||
<script defer type="text/javascript" src="{% static 'fullcalendar/main.min.js' %}"></script>
|
||||
<script defer type="text/javascript" src="{% static 'fullcalendar/locales-all.min.js' %}"></script>
|
||||
<script defer type="text/javascript" src="{% static 'select2/js/select2.full.min.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% static 'script/moment.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% static 'script/chart.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% static 'script/chartjs-adapter-moment.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% static 'script/clipboard.min.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% static 'easymde/easymde.min.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% static 'script/randomColor.min.js' %}"></script>
|
||||
<script defer type='text/javascript' src="{% static 'script/qr-scanner.umd.min.js' %}"></script>
|
@ -6,6 +6,7 @@ from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
from django.contrib.contenttypes.fields import GenericForeignKey
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.core.cache import cache
|
||||
from django.db import models
|
||||
from django.db.models import Q, UniqueConstraint
|
||||
from django.db.models.signals import post_delete, post_save
|
||||
@ -474,13 +475,19 @@ def update_group_roles(group, debug=False):
|
||||
logger.info(f"Adding permission {child_perm} to group {group.name}")
|
||||
|
||||
|
||||
@receiver(post_save, sender=Group, dispatch_uid='create_missing_rule_sets')
|
||||
def create_missing_rule_sets(sender, instance, **kwargs):
|
||||
"""Called *after* a Group object is saved.
|
||||
def clear_user_role_cache(user):
|
||||
"""Remove user role permission information from the cache.
|
||||
|
||||
As the linked RuleSet instances are saved *before* the Group, then we can now use these RuleSet values to update the group permissions.
|
||||
- This function is called whenever the user / group is updated
|
||||
|
||||
Args:
|
||||
user: The User object to be expunged from the cache
|
||||
"""
|
||||
update_group_roles(instance)
|
||||
|
||||
for role in RuleSet.RULESET_MODELS.keys():
|
||||
for perm in ['add', 'change', 'view', 'delete']:
|
||||
key = f"role_{user}_{role}_{perm}"
|
||||
cache.delete(key)
|
||||
|
||||
|
||||
def check_user_role(user, role, permission):
|
||||
@ -491,6 +498,17 @@ def check_user_role(user, role, permission):
|
||||
if user.is_superuser:
|
||||
return True
|
||||
|
||||
# First, check the cache
|
||||
key = f"role_{user}_{role}_{permission}"
|
||||
|
||||
result = cache.get(key)
|
||||
|
||||
if result is not None:
|
||||
return result
|
||||
|
||||
# Default for no match
|
||||
result = False
|
||||
|
||||
for group in user.groups.all():
|
||||
|
||||
for rule in group.rule_sets.all():
|
||||
@ -498,19 +516,24 @@ def check_user_role(user, role, permission):
|
||||
if rule.name == role:
|
||||
|
||||
if permission == 'add' and rule.can_add:
|
||||
return True
|
||||
result = True
|
||||
break
|
||||
|
||||
if permission == 'change' and rule.can_change:
|
||||
return True
|
||||
result = True
|
||||
break
|
||||
|
||||
if permission == 'view' and rule.can_view:
|
||||
return True
|
||||
result = True
|
||||
break
|
||||
|
||||
if permission == 'delete' and rule.can_delete:
|
||||
return True
|
||||
result = True
|
||||
break
|
||||
|
||||
# No matching permissions found
|
||||
return False
|
||||
# Save result to cache
|
||||
cache.set(key, result, timeout=3600)
|
||||
return result
|
||||
|
||||
|
||||
class Owner(models.Model):
|
||||
@ -659,3 +682,22 @@ def delete_owner(sender, instance, **kwargs):
|
||||
"""Callback function to delete an owner instance after either a new group or user instance is deleted."""
|
||||
owner = Owner.get_owner(instance)
|
||||
owner.delete()
|
||||
|
||||
|
||||
@receiver(post_save, sender=get_user_model(), dispatch_uid='clear_user_cache')
|
||||
def clear_user_cache(sender, instance, **kwargs):
|
||||
"""Callback function when a user object is saved"""
|
||||
|
||||
clear_user_role_cache(instance)
|
||||
|
||||
|
||||
@receiver(post_save, sender=Group, dispatch_uid='create_missing_rule_sets')
|
||||
def create_missing_rule_sets(sender, instance, **kwargs):
|
||||
"""Called *after* a Group object is saved.
|
||||
|
||||
As the linked RuleSet instances are saved *before* the Group, then we can now use these RuleSet values to update the group permissions.
|
||||
"""
|
||||
update_group_roles(instance)
|
||||
|
||||
for user in get_user_model().objects.filter(groups__name=instance.name):
|
||||
clear_user_role_cache(user)
|
||||
|
Loading…
Reference in New Issue
Block a user