mirror of
https://github.com/inventree/InvenTree.git
synced 2026-05-06 14:59:57 -05: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:
@@ -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',
|
||||
|
||||
File diff suppressed because one or more lines are too long
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;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
|
||||
}());
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
}());
|
||||
@@ -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;
|
||||
|
||||
@@ -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
File diff suppressed because one or more lines are too long
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(),
|
||||
)
|
||||
|
||||
+86
-34
@@ -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 %}
|
||||
|
||||
+84
-105
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
+53
-11
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user