Compare commits
19 Commits
develop
...
feature/te
| Author | SHA1 | Date | |
|---|---|---|---|
| 529872b590 | |||
| f1e1355986 | |||
| e911da20ef | |||
| 2ee9af4b9d | |||
| 5e2cb3f745 | |||
| 704c58222c | |||
| 401e0d4ae8 | |||
|
|
c0902c65ee | ||
| 8fe2a9ebca | |||
| d62250fb4f | |||
| 28404fb8b6 | |||
| 40e1c91755 | |||
| 7921a138d4 | |||
| 0c8a5cfc2c | |||
| 27179da2a2 | |||
| df050cb83b | |||
|
|
5e50e09966 | ||
| 0bd526e08e | |||
|
|
8df9034574 |
@ -8,8 +8,8 @@
|
||||
<SignAssembly>True</SignAssembly>
|
||||
<StartupObject>BreCalClient.App</StartupObject>
|
||||
<AssemblyOriginatorKeyFile>..\..\misc\brecal.snk</AssemblyOriginatorKeyFile>
|
||||
<AssemblyVersion>1.5.0.4</AssemblyVersion>
|
||||
<FileVersion>1.5.0.4</FileVersion>
|
||||
<AssemblyVersion>1.5.0.7</AssemblyVersion>
|
||||
<FileVersion>1.5.0.7</FileVersion>
|
||||
<Title>Bremen calling client</Title>
|
||||
<Description>A Windows WPF client for the Bremen calling API.</Description>
|
||||
<ApplicationIcon>containership.ico</ApplicationIcon>
|
||||
|
||||
@ -85,7 +85,7 @@
|
||||
<Label x:Name="labelBSMDGranted" Grid.Row="7" Grid.Column="3" Grid.ColumnSpan="1" Content="{x:Static p:Resources.textBSMDGranted}" Visibility="Hidden" FontWeight="DemiBold" />
|
||||
|
||||
<Label x:Name="labelShiftingCount" Grid.Row="6" Grid.Column="2" HorizontalAlignment="Right" Content="{x:Static p:Resources.textShiftingSequence}" />
|
||||
<xctk:IntegerUpDown x:Name="integerUpDownShiftingCount" Grid.Row="6" Grid.Column="3" Margin="2" Minimum="0" Maximum="255" />
|
||||
<xctk:IntegerUpDown x:Name="integerUpDownShiftingCount" Grid.Row="6" Grid.Column="3" Margin="2" Minimum="0" Maximum="127" />
|
||||
|
||||
<StackPanel Grid.Row="8" Grid.Column="3" Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<Button Width= "80" Margin="2" Content="{x:Static p:Resources.textOK}" x:Name="buttonOK" Click="buttonOK_Click" IsEnabled="False" />
|
||||
|
||||
@ -73,7 +73,7 @@ namespace BreCalClient
|
||||
{
|
||||
if (!CheckValues(out string message))
|
||||
{
|
||||
MessageBox.Show(message, "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -145,6 +145,13 @@ namespace BreCalClient
|
||||
return false;
|
||||
}
|
||||
|
||||
if((this.datePickerETA_End.Value.HasValue && !this.datePickerETA.Value.HasValue) ||
|
||||
(this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue))
|
||||
{
|
||||
message = BreCalClient.Resources.Resources.textStartTimeMissing;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -82,7 +82,7 @@ namespace BreCalClient
|
||||
{
|
||||
if (!CheckValues(out string message))
|
||||
{
|
||||
System.Windows.MessageBox.Show(message, "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -155,6 +155,13 @@ namespace BreCalClient
|
||||
return false;
|
||||
}
|
||||
|
||||
if((this.datePickerETD_End.Value.HasValue && !this.datePickerETD.Value.HasValue) ||
|
||||
(this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue))
|
||||
{
|
||||
message = BreCalClient.Resources.Resources.textStartTimeMissing;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -73,7 +73,7 @@ namespace BreCalClient
|
||||
{
|
||||
if (!CheckValues(out string message))
|
||||
{
|
||||
System.Windows.MessageBox.Show(message, "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -164,6 +164,14 @@ namespace BreCalClient
|
||||
return false;
|
||||
}
|
||||
|
||||
if((this.datePickerETA_End.Value.HasValue && !this.datePickerETA.Value.HasValue) ||
|
||||
(this.datePickerETD_End.Value.HasValue && !this.datePickerETD.Value.HasValue) ||
|
||||
(this.datePickerTidalWindowTo.Value.HasValue && !this.datePickerTidalWindowFrom.Value.HasValue))
|
||||
{
|
||||
message = BreCalClient.Resources.Resources.textStartTimeMissing;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -49,7 +49,8 @@ namespace BreCalClient
|
||||
{
|
||||
if (!CheckValues(out string message))
|
||||
{
|
||||
System.Windows.MessageBox.Show(message, "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning,
|
||||
MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -116,6 +117,24 @@ namespace BreCalClient
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.datePickerETDBerth.Value.HasValue && (this.datePickerETDBerth.Value.Value < DateTime.Now) && (this.datePickerETABerth_End.Value == null))
|
||||
{
|
||||
message = BreCalClient.Resources.Resources.textETDInThePast;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.datePickerETDBerth_End.Value.HasValue && this.datePickerETDBerth_End.Value < DateTime.Now)
|
||||
{
|
||||
message = BreCalClient.Resources.Resources.textETDInThePast;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.datePickerETDBerth.Value.HasValue && this.datePickerETDBerth_End.Value.HasValue && this.datePickerETDBerth.Value > this.datePickerETDBerth_End.Value)
|
||||
{
|
||||
message = BreCalClient.Resources.Resources.textEndValueBeforeStartValue;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.datePickerLockTime.Value.HasValue && (this.datePickerLockTime.Value.Value < DateTime.Now))
|
||||
{
|
||||
message = BreCalClient.Resources.Resources.textLockTimeInThePast;
|
||||
@ -135,6 +154,13 @@ namespace BreCalClient
|
||||
return false;
|
||||
}
|
||||
|
||||
if((this.datePickerETABerth_End.Value.HasValue && !this.datePickerETABerth.Value.HasValue) ||
|
||||
(this.datePickerETDBerth_End.Value.HasValue && !this.datePickerETDBerth.Value.HasValue))
|
||||
{
|
||||
message = BreCalClient.Resources.Resources.textStartTimeMissing;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -64,7 +64,7 @@ namespace BreCalClient
|
||||
{
|
||||
if (!CheckValues(out string message))
|
||||
{
|
||||
System.Windows.MessageBox.Show(message, "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
System.Windows.MessageBox.Show(message, BreCalClient.Resources.Resources.textWarning, MessageBoxButton.OK, MessageBoxImage.Warning);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -150,6 +150,13 @@ namespace BreCalClient
|
||||
return false;
|
||||
}
|
||||
|
||||
if((this.datePickerOperationEnd_End.Value.HasValue && !this.datePickerOperationEnd.Value.HasValue) ||
|
||||
(this.datePickerOperationStart_End.Value.HasValue && !this.datePickerOperationStart.Value.HasValue))
|
||||
{
|
||||
message = BreCalClient.Resources.Resources.textStartTimeMissing;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<ApplicationRevision>0</ApplicationRevision>
|
||||
<ApplicationVersion>1.5.0.4</ApplicationVersion>
|
||||
<ApplicationVersion>1.5.0.7</ApplicationVersion>
|
||||
<BootstrapperEnabled>True</BootstrapperEnabled>
|
||||
<Configuration>Debug</Configuration>
|
||||
<CreateDesktopShortcut>True</CreateDesktopShortcut>
|
||||
|
||||
27
src/BreCalClient/Resources/Resources.Designer.cs
generated
27
src/BreCalClient/Resources/Resources.Designer.cs
generated
@ -119,6 +119,15 @@ namespace BreCalClient.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
public static string arrow_right_blue {
|
||||
get {
|
||||
return ResourceManager.GetString("arrow_right_blue", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Byte[].
|
||||
/// </summary>
|
||||
@ -139,6 +148,15 @@ namespace BreCalClient.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to .
|
||||
/// </summary>
|
||||
public static string arrow_up_green {
|
||||
get {
|
||||
return ResourceManager.GetString("arrow_up_green", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized resource of type System.Byte[].
|
||||
/// </summary>
|
||||
@ -1199,6 +1217,15 @@ namespace BreCalClient.Resources {
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to If an end time is set, a start time is also required.
|
||||
/// </summary>
|
||||
public static string textStartTimeMissing {
|
||||
get {
|
||||
return ResourceManager.GetString("textStartTimeMissing", resourceCulture);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Looks up a localized string similar to Terminal.
|
||||
/// </summary>
|
||||
|
||||
@ -117,16 +117,13 @@
|
||||
<resheader name="writer">
|
||||
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</resheader>
|
||||
<data name="Arrival" xml:space="preserve">
|
||||
<value>Einkommend</value>
|
||||
</data>
|
||||
<assembly alias="System.Windows.Forms" name="System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
|
||||
<data name="arrow_down_red" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>arrow_down_red.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="arrow_right_blue" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>arrow_right_blue.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="arrow_up_green" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>arrow_up_green.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="clipboard" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>clipboard.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
@ -136,15 +133,24 @@
|
||||
<data name="containership" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>containership.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="Departure" xml:space="preserve">
|
||||
<value>Ausgehend</value>
|
||||
</data>
|
||||
<data name="emergency_stop_button" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>emergency_stop_button.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="logo_bremen_calling" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>logo_bremen_calling.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="Shifting" xml:space="preserve">
|
||||
<value>Verholung</value>
|
||||
</data>
|
||||
<data name="ship2" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>ship2.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="textAdd" xml:space="preserve">
|
||||
<value>Hinzufügen</value>
|
||||
</data>
|
||||
<data name="textAgencies" xml:space="preserve">
|
||||
<value>Agenturen</value>
|
||||
</data>
|
||||
@ -160,9 +166,18 @@
|
||||
<data name="textBerth" xml:space="preserve">
|
||||
<value>Liegeplatz</value>
|
||||
</data>
|
||||
<data name="textBerthRemarks" xml:space="preserve">
|
||||
<value>Liegeplatz Informationen</value>
|
||||
</data>
|
||||
<data name="textBerths" xml:space="preserve">
|
||||
<value>Liegeplätze</value>
|
||||
</data>
|
||||
<data name="textBothTideTimesNecessary" xml:space="preserve">
|
||||
<value>Beide Tidenzeiten sollten angegeben werden (von - bis)</value>
|
||||
</data>
|
||||
<data name="textBSMDGranted" xml:space="preserve">
|
||||
<value>Freigabe zur Bearb. f. BSMD erteilt</value>
|
||||
</data>
|
||||
<data name="textBunkering" xml:space="preserve">
|
||||
<value>Bunkeraufnahme</value>
|
||||
</data>
|
||||
@ -181,59 +196,122 @@
|
||||
<data name="textChange" xml:space="preserve">
|
||||
<value>Ändern</value>
|
||||
</data>
|
||||
<data name="textChangeContactInfo" xml:space="preserve">
|
||||
<value>Kontaktdaten bearbeiten</value>
|
||||
</data>
|
||||
<data name="textChangeHistory" xml:space="preserve">
|
||||
<value>Verlauf</value>
|
||||
</data>
|
||||
<data name="textChangePassword" xml:space="preserve">
|
||||
<value>Passwort ändern</value>
|
||||
</data>
|
||||
<data name="textClearAll" xml:space="preserve">
|
||||
<value>Alle Eintragungen zurücksetzen?</value>
|
||||
</data>
|
||||
<data name="textClearAssignment" xml:space="preserve">
|
||||
<value>Zuordnung entfernen</value>
|
||||
</data>
|
||||
<data name="textClearFilters" xml:space="preserve">
|
||||
<value>Filter löschen</value>
|
||||
</data>
|
||||
<data name="textClearValue" xml:space="preserve">
|
||||
<value>Eingabe löschen</value>
|
||||
</data>
|
||||
<data name="textClose" xml:space="preserve">
|
||||
<value>Schliessen</value>
|
||||
</data>
|
||||
<data name="textConfirmation" xml:space="preserve">
|
||||
<value>Bestätigung</value>
|
||||
</data>
|
||||
<data name="textDelete" xml:space="preserve">
|
||||
<value>Löschen</value>
|
||||
</data>
|
||||
<data name="textDeleted" xml:space="preserve">
|
||||
<value>Gelöscht</value>
|
||||
</data>
|
||||
<data name="textDepartureTerminal" xml:space="preserve">
|
||||
<value>Terminal Abfahrt</value>
|
||||
</data>
|
||||
<data name="textDraft" xml:space="preserve">
|
||||
<value>Tiefgang (m)</value>
|
||||
</data>
|
||||
<data name="textEdit" xml:space="preserve">
|
||||
<value>Bearbeiten</value>
|
||||
</data>
|
||||
<data name="textEditShip" xml:space="preserve">
|
||||
<value>Schiff bearbeiten</value>
|
||||
</data>
|
||||
<data name="textEditShipcall" xml:space="preserve">
|
||||
<value>Schiffsanlauf bearbeiten</value>
|
||||
</data>
|
||||
<data name="textEditShips" xml:space="preserve">
|
||||
<value>Schiffe anlegen / bearbeiten</value>
|
||||
</data>
|
||||
<data name="textEditTimes" xml:space="preserve">
|
||||
<value>Zeiten bearbeiten</value>
|
||||
</data>
|
||||
<data name="textETABerth" xml:space="preserve">
|
||||
<value>ETA Liegeplatz</value>
|
||||
<data name="textEmail" xml:space="preserve">
|
||||
<value>E-Mail</value>
|
||||
</data>
|
||||
<data name="textETDBerth" xml:space="preserve">
|
||||
<value>ETD Liegeplatz</value>
|
||||
<data name="textEndValueBeforeStartValue" xml:space="preserve">
|
||||
<value>Endzeit liegt vor Startzeit</value>
|
||||
</data>
|
||||
<data name="textEnterKeyword" xml:space="preserve">
|
||||
<value>Schiff / Bemerkung</value>
|
||||
</data>
|
||||
<data name="textError" xml:space="preserve">
|
||||
<value>Error</value>
|
||||
</data>
|
||||
<data name="textETABerth" xml:space="preserve">
|
||||
<value>ETA Liegeplatz</value>
|
||||
</data>
|
||||
<data name="textETAInThePast" xml:space="preserve">
|
||||
<value>Zeitpunkt ETA liegt in der Vergangenheit</value>
|
||||
</data>
|
||||
<data name="textETDBerth" xml:space="preserve">
|
||||
<value>ETD Liegeplatz</value>
|
||||
</data>
|
||||
<data name="textETDInThePast" xml:space="preserve">
|
||||
<value>Zeitpunkt ETD liegt in der Vergangenheit</value>
|
||||
</data>
|
||||
<data name="textExit" xml:space="preserve">
|
||||
<value>Verlassen</value>
|
||||
</data>
|
||||
<data name="textFrom" xml:space="preserve">
|
||||
<value>von</value>
|
||||
</data>
|
||||
<data name="textInterval" xml:space="preserve">
|
||||
<value>Zeitraum</value>
|
||||
</data>
|
||||
<data name="textLengthWidth" xml:space="preserve">
|
||||
<value>L/B (m)</value>
|
||||
</data>
|
||||
<data name="textLogin" xml:space="preserve">
|
||||
<value>Anmelden</value>
|
||||
</data>
|
||||
<data name="textFixed" xml:space="preserve">
|
||||
<value>Fest</value>
|
||||
</data>
|
||||
<data name="textFixedOrder" xml:space="preserve">
|
||||
<value>Als feste Bestellung vermerkt</value>
|
||||
</data>
|
||||
<data name="textFrom" xml:space="preserve">
|
||||
<value>von</value>
|
||||
</data>
|
||||
<data name="textIncoming" xml:space="preserve">
|
||||
<value>Einkommend</value>
|
||||
</data>
|
||||
<data name="textInfoChangePW" xml:space="preserve">
|
||||
<value>App Info anzeigen und Passwort ändern</value>
|
||||
</data>
|
||||
<data name="textInterval" xml:space="preserve">
|
||||
<value>Zeitraum</value>
|
||||
</data>
|
||||
<data name="textLength" xml:space="preserve">
|
||||
<value>Länge</value>
|
||||
</data>
|
||||
<data name="textLengthWidth" xml:space="preserve">
|
||||
<value>L/B (m)</value>
|
||||
</data>
|
||||
<data name="textLockTime" xml:space="preserve">
|
||||
<value>Zeit Schleuse</value>
|
||||
</data>
|
||||
<data name="textOperationsEnd" xml:space="preserve">
|
||||
<value>Operation Ende</value>
|
||||
<data name="textLockTimeInThePast" xml:space="preserve">
|
||||
<value>Schleusenzeit liegt in der Vergangenheit</value>
|
||||
</data>
|
||||
<data name="textOperationsStart" xml:space="preserve">
|
||||
<value>Operation Start</value>
|
||||
<data name="textLogin" xml:space="preserve">
|
||||
<value>Anmelden</value>
|
||||
</data>
|
||||
<data name="textMineOnly" xml:space="preserve">
|
||||
<value>nur eigene</value>
|
||||
</data>
|
||||
<data name="textMooredLock" xml:space="preserve">
|
||||
<value>auch in Schleuse</value>
|
||||
@ -250,21 +328,45 @@
|
||||
<data name="textNotRotated" xml:space="preserve">
|
||||
<value>Ungedreht</value>
|
||||
</data>
|
||||
<data name="textZoneEntryTime" xml:space="preserve">
|
||||
<value>Reviereintritt</value>
|
||||
</data>
|
||||
<data name="textOK" xml:space="preserve">
|
||||
<value>OK</value>
|
||||
</data>
|
||||
<data name="textOldPassword" xml:space="preserve">
|
||||
<value>Altes Passwort</value>
|
||||
</data>
|
||||
<data name="textOperation" xml:space="preserve">
|
||||
<value>Vorgang</value>
|
||||
</data>
|
||||
<data name="textOperationEndInThePast" xml:space="preserve">
|
||||
<value>Operation Endzeit liegt in der Vergangenheit</value>
|
||||
</data>
|
||||
<data name="textOperationsEnd" xml:space="preserve">
|
||||
<value>Operation Ende</value>
|
||||
</data>
|
||||
<data name="textOperationsStart" xml:space="preserve">
|
||||
<value>Operation Start</value>
|
||||
</data>
|
||||
<data name="textOperationStartInThePast" xml:space="preserve">
|
||||
<value>Operation Startzeit liegt in der Vergangenheit</value>
|
||||
</data>
|
||||
<data name="textOutgoing" xml:space="preserve">
|
||||
<value>Ausgehend</value>
|
||||
</data>
|
||||
<data name="textParticipant" xml:space="preserve">
|
||||
<value>Teilnehmer</value>
|
||||
</data>
|
||||
<data name="textParticipants" xml:space="preserve">
|
||||
<value>Teilnehmer</value>
|
||||
</data>
|
||||
<data name="textPassword" xml:space="preserve">
|
||||
<value>Passwort</value>
|
||||
</data>
|
||||
<data name="textPasswordChanged" xml:space="preserve">
|
||||
<value>Passwort geändert.</value>
|
||||
</data>
|
||||
<data name="textPhone" xml:space="preserve">
|
||||
<value>Telefon</value>
|
||||
</data>
|
||||
<data name="textPierside" xml:space="preserve">
|
||||
<value>Anlegeseite</value>
|
||||
</data>
|
||||
@ -274,12 +376,24 @@
|
||||
<data name="textPilotRequired" xml:space="preserve">
|
||||
<value>Lotsorder</value>
|
||||
</data>
|
||||
<data name="textPilots" xml:space="preserve">
|
||||
<value>Flusslotsen</value>
|
||||
</data>
|
||||
<data name="textPort" xml:space="preserve">
|
||||
<value>Backbord</value>
|
||||
</data>
|
||||
<data name="textPortAuthority" xml:space="preserve">
|
||||
<value>Hafenamt</value>
|
||||
</data>
|
||||
<data name="textRainSensitiveCargo" xml:space="preserve">
|
||||
<value>Regensensitive Ladung</value>
|
||||
</data>
|
||||
<data name="textRecommendedTugs" xml:space="preserve">
|
||||
<value>Anzahl Schlepper</value>
|
||||
</data>
|
||||
<data name="textRemarks" xml:space="preserve">
|
||||
<value>Info</value>
|
||||
</data>
|
||||
<data name="textRepeatNewPassword" xml:space="preserve">
|
||||
<value>Neues Passwort wiederholen</value>
|
||||
</data>
|
||||
@ -295,27 +409,75 @@
|
||||
<data name="textSearch" xml:space="preserve">
|
||||
<value>Suche</value>
|
||||
</data>
|
||||
<data name="textShifting" xml:space="preserve">
|
||||
<value>Verholung</value>
|
||||
</data>
|
||||
<data name="textShiftingFrom" xml:space="preserve">
|
||||
<value>Verholung von</value>
|
||||
</data>
|
||||
<data name="textShiftingSequence" xml:space="preserve">
|
||||
<value>Verhol. Nr.</value>
|
||||
</data>
|
||||
<data name="textShiftingTo" xml:space="preserve">
|
||||
<value>Verholung nach</value>
|
||||
</data>
|
||||
<data name="textShip" xml:space="preserve">
|
||||
<value>Schiff</value>
|
||||
</data>
|
||||
<data name="textShipLength" xml:space="preserve">
|
||||
<value>Schiffslänge</value>
|
||||
</data>
|
||||
<data name="textShips" xml:space="preserve">
|
||||
<value>Schiffe</value>
|
||||
</data>
|
||||
<data name="textShowCancelledShipcalls" xml:space="preserve">
|
||||
<value>Stornierte anzeigen</value>
|
||||
</data>
|
||||
<data name="textShowHistory" xml:space="preserve">
|
||||
<value>Änderungshistorie der Anläufe anzeigen</value>
|
||||
</data>
|
||||
<data name="textSortOrder" xml:space="preserve">
|
||||
<value>Sortierung</value>
|
||||
</data>
|
||||
<data name="textStarboard" xml:space="preserve">
|
||||
<value>Steuerbord</value>
|
||||
</data>
|
||||
<data name="textTerminal" xml:space="preserve">
|
||||
<value>Terminal</value>
|
||||
</data>
|
||||
<data name="textTidalBothValues" xml:space="preserve">
|
||||
<value>Für das Tidenfenster müssen beide Zeiten angegeben werden</value>
|
||||
</data>
|
||||
<data name="textTidalWindow" xml:space="preserve">
|
||||
<value>Tidenfenster</value>
|
||||
</data>
|
||||
<data name="textTideTimesInThePast" xml:space="preserve">
|
||||
<value>Tidenzeit liegt in der Vergangenheit</value>
|
||||
</data>
|
||||
<data name="textTimestamp" xml:space="preserve">
|
||||
<value>Zeitpunkt</value>
|
||||
</data>
|
||||
<data name="textTo" xml:space="preserve">
|
||||
<value>bis</value>
|
||||
</data>
|
||||
<data name="textTooFarInTheFuture" xml:space="preserve">
|
||||
<value>Eine Zeiteingabe ist zu weit in der Zukunft</value>
|
||||
</data>
|
||||
<data name="textTooltipSetFixedOrder" xml:space="preserve">
|
||||
<value>Als feste Bestellung vermerken</value>
|
||||
</data>
|
||||
<data name="textTooltipUnSetFixedOrder" xml:space="preserve">
|
||||
<value>Feste Bestellung zurücknehmen</value>
|
||||
</data>
|
||||
<data name="textTriggerManualRefresh" xml:space="preserve">
|
||||
<value>Manuelle Aktualisierung der Anläufe auslösen</value>
|
||||
</data>
|
||||
<data name="textTug" xml:space="preserve">
|
||||
<value>Schlepper</value>
|
||||
</data>
|
||||
<data name="textTugCompany" xml:space="preserve">
|
||||
<value>Schlepper-Reederei</value>
|
||||
</data>
|
||||
<data name="textTugRequired" xml:space="preserve">
|
||||
<value>Schlepperorder</value>
|
||||
</data>
|
||||
@ -328,9 +490,27 @@
|
||||
<data name="textUsername" xml:space="preserve">
|
||||
<value>Benutzername</value>
|
||||
</data>
|
||||
<data name="textUserNamePasswordEmpty" xml:space="preserve">
|
||||
<value>Benutzername / Passwort leer!</value>
|
||||
</data>
|
||||
<data name="textVoyage" xml:space="preserve">
|
||||
<value>Reise</value>
|
||||
</data>
|
||||
<data name="textWarning" xml:space="preserve">
|
||||
<value>Warnung</value>
|
||||
</data>
|
||||
<data name="textWidth" xml:space="preserve">
|
||||
<value>Breite</value>
|
||||
</data>
|
||||
<data name="textWrongCredentials" xml:space="preserve">
|
||||
<value>Benutzername / Passwort falsch</value>
|
||||
</data>
|
||||
<data name="textZoneEntryInThePast" xml:space="preserve">
|
||||
<value>Zeit Reviereintritt liegt in der Vergangenheit</value>
|
||||
</data>
|
||||
<data name="textZoneEntryTime" xml:space="preserve">
|
||||
<value>Reviereintritt</value>
|
||||
</data>
|
||||
<data name="trafficlight_green" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>trafficlight_green.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
@ -355,196 +535,19 @@
|
||||
<data name="umbrella_open" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>umbrella_open.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="worker2" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>worker2.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="textClearAssignment" xml:space="preserve">
|
||||
<value>Zuordnung entfernen</value>
|
||||
</data>
|
||||
<data name="textClearValue" xml:space="preserve">
|
||||
<value>Eingabe löschen</value>
|
||||
</data>
|
||||
<data name="textConfirmation" xml:space="preserve">
|
||||
<value>Bestätigung</value>
|
||||
</data>
|
||||
<data name="textPasswordChanged" xml:space="preserve">
|
||||
<value>Passwort geändert.</value>
|
||||
</data>
|
||||
<data name="textClearFilters" xml:space="preserve">
|
||||
<value>Filter löschen</value>
|
||||
</data>
|
||||
<data name="textShowCancelledShipcalls" xml:space="preserve">
|
||||
<value>Stornierte anzeigen</value>
|
||||
</data>
|
||||
<data name="textBerthRemarks" xml:space="preserve">
|
||||
<value>Liegeplatz Informationen</value>
|
||||
</data>
|
||||
<data name="textBSMDGranted" xml:space="preserve">
|
||||
<value>Freigabe zur Bearb. f. BSMD erteilt</value>
|
||||
</data>
|
||||
<data name="textRemarks" xml:space="preserve">
|
||||
<value>Info</value>
|
||||
</data>
|
||||
<data name="textIncoming" xml:space="preserve">
|
||||
<value>Einkommend</value>
|
||||
</data>
|
||||
<data name="textOutgoing" xml:space="preserve">
|
||||
<value>Ausgehend</value>
|
||||
</data>
|
||||
<data name="textShifting" xml:space="preserve">
|
||||
<value>Verholung</value>
|
||||
</data>
|
||||
<data name="textLength" xml:space="preserve">
|
||||
<value>Länge</value>
|
||||
</data>
|
||||
<data name="textShiftingFrom" xml:space="preserve">
|
||||
<value>Verholung von</value>
|
||||
</data>
|
||||
<data name="textShiftingTo" xml:space="preserve">
|
||||
<value>Verholung nach</value>
|
||||
</data>
|
||||
<data name="textWidth" xml:space="preserve">
|
||||
<value>Breite</value>
|
||||
</data>
|
||||
<data name="textChangeContactInfo" xml:space="preserve">
|
||||
<value>Kontaktdaten bearbeiten</value>
|
||||
</data>
|
||||
<data name="textEmail" xml:space="preserve">
|
||||
<value>E-Mail</value>
|
||||
</data>
|
||||
<data name="textPhone" xml:space="preserve">
|
||||
<value>Telefon</value>
|
||||
</data>
|
||||
<data name="textWrongCredentials" xml:space="preserve">
|
||||
<value>Benutzername / Passwort falsch</value>
|
||||
</data>
|
||||
<data name="textUserNamePasswordEmpty" xml:space="preserve">
|
||||
<value>Benutzername / Passwort leer!</value>
|
||||
</data>
|
||||
<data name="textPort" xml:space="preserve">
|
||||
<value>Backbord</value>
|
||||
</data>
|
||||
<data name="textStarboard" xml:space="preserve">
|
||||
<value>Steuerbord</value>
|
||||
</data>
|
||||
<data name="textAdd" xml:space="preserve">
|
||||
<value>Hinzufügen</value>
|
||||
</data>
|
||||
<data name="textDelete" xml:space="preserve">
|
||||
<value>Löschen</value>
|
||||
</data>
|
||||
<data name="textEdit" xml:space="preserve">
|
||||
<value>Bearbeiten</value>
|
||||
</data>
|
||||
<data name="textEditShips" xml:space="preserve">
|
||||
<value>Schiffe anlegen / bearbeiten</value>
|
||||
</data>
|
||||
<data name="textDeleted" xml:space="preserve">
|
||||
<value>Gelöscht</value>
|
||||
</data>
|
||||
<data name="textEditShip" xml:space="preserve">
|
||||
<value>Schiff bearbeiten</value>
|
||||
</data>
|
||||
<data name="textTugCompany" xml:space="preserve">
|
||||
<value>Schlepper-Reederei</value>
|
||||
</data>
|
||||
<data name="textChangeHistory" xml:space="preserve">
|
||||
<value>Verlauf</value>
|
||||
</data>
|
||||
<data name="textInfoChangePW" xml:space="preserve">
|
||||
<value>App Info anzeigen und Passwort ändern</value>
|
||||
</data>
|
||||
<data name="textShowHistory" xml:space="preserve">
|
||||
<value>Änderungshistorie der Anläufe anzeigen</value>
|
||||
</data>
|
||||
<data name="textMineOnly" xml:space="preserve">
|
||||
<value>nur eigene</value>
|
||||
</data>
|
||||
<data name="textOperation" xml:space="preserve">
|
||||
<value>Vorgang</value>
|
||||
</data>
|
||||
<data name="textParticipant" xml:space="preserve">
|
||||
<value>Teilnehmer</value>
|
||||
</data>
|
||||
<data name="textTimestamp" xml:space="preserve">
|
||||
<value>Zeitpunkt</value>
|
||||
</data>
|
||||
<data name="textFixedOrder" xml:space="preserve">
|
||||
<value>Als feste Bestellung vermerkt</value>
|
||||
</data>
|
||||
<data name="textTooltipSetFixedOrder" xml:space="preserve">
|
||||
<value>Als feste Bestellung vermerken</value>
|
||||
</data>
|
||||
<data name="textTooltipUnSetFixedOrder" xml:space="preserve">
|
||||
<value>Feste Bestellung zurücknehmen</value>
|
||||
</data>
|
||||
<data name="textTriggerManualRefresh" xml:space="preserve">
|
||||
<value>Manuelle Aktualisierung der Anläufe auslösen</value>
|
||||
</data>
|
||||
<data name="Arrival" xml:space="preserve">
|
||||
<value>Einkommend</value>
|
||||
</data>
|
||||
<data name="Departure" xml:space="preserve">
|
||||
<value>Ausgehend</value>
|
||||
</data>
|
||||
<data name="Shifting" xml:space="preserve">
|
||||
<value>Verholung</value>
|
||||
</data>
|
||||
<data name="Undefined" xml:space="preserve">
|
||||
<value>Unbekannt</value>
|
||||
</data>
|
||||
<data name="textShips" xml:space="preserve">
|
||||
<value>Schiffe</value>
|
||||
<data name="worker2" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>worker2.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="textPilots" xml:space="preserve">
|
||||
<value>Flusslotsen</value>
|
||||
<data name="arrow_right_blue" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>arrow_right_blue.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="textPortAuthority" xml:space="preserve">
|
||||
<value>Hafenamt</value>
|
||||
<data name="arrow_up_green" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>arrow_up_green.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="textShiftingSequence" xml:space="preserve">
|
||||
<value>Verhol. Nr.</value>
|
||||
</data>
|
||||
<data name="textClearAll" xml:space="preserve">
|
||||
<value>Alle Eintragungen zurücksetzen?</value>
|
||||
</data>
|
||||
<data name="textBothTideTimesNecessary" xml:space="preserve">
|
||||
<value>Beide Tidenzeiten sollten angegeben werden (von - bis)</value>
|
||||
</data>
|
||||
<data name="textEndValueBeforeStartValue" xml:space="preserve">
|
||||
<value>Endzeit liegt vor Startzeit</value>
|
||||
</data>
|
||||
<data name="textError" xml:space="preserve">
|
||||
<value>Error</value>
|
||||
</data>
|
||||
<data name="textETAInThePast" xml:space="preserve">
|
||||
<value>Zeitpunkt ETA liegt in der Vergangenheit</value>
|
||||
</data>
|
||||
<data name="textETDInThePast" xml:space="preserve">
|
||||
<value>Zeitpunkt ETD liegt in der Vergangenheit</value>
|
||||
</data>
|
||||
<data name="textLockTimeInThePast" xml:space="preserve">
|
||||
<value>Schleusenzeit liegt in der Vergangenheit</value>
|
||||
</data>
|
||||
<data name="textOperationEndInThePast" xml:space="preserve">
|
||||
<value>Operation Endzeit liegt in der Vergangenheit</value>
|
||||
</data>
|
||||
<data name="textOperationStartInThePast" xml:space="preserve">
|
||||
<value>Operation Startzeit liegt in der Vergangenheit</value>
|
||||
</data>
|
||||
<data name="textTideTimesInThePast" xml:space="preserve">
|
||||
<value>Tidenzeit liegt in der Vergangenheit</value>
|
||||
</data>
|
||||
<data name="textWarning" xml:space="preserve">
|
||||
<value>Warnung</value>
|
||||
</data>
|
||||
<data name="textZoneEntryInThePast" xml:space="preserve">
|
||||
<value>Zeit Reviereintritt liegt in der Vergangenheit</value>
|
||||
</data>
|
||||
<data name="textTidalBothValues" xml:space="preserve">
|
||||
<value>Für das Tidenfenster müssen beide Zeiten angegeben werden</value>
|
||||
</data>
|
||||
<data name="textTooFarInTheFuture" xml:space="preserve">
|
||||
<value>Eine Zeiteingabe ist zu weit in der Zukunft</value>
|
||||
<data name="textStartTimeMissing" xml:space="preserve">
|
||||
<value>Wenn eine Ende-Zeit angegeben wird, muss auch eine Start-Zeit angegeben werden</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -589,4 +589,13 @@
|
||||
<data name="_lock" type="System.Resources.ResXFileRef, System.Windows.Forms">
|
||||
<value>lock.png;System.Byte[], mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
|
||||
</data>
|
||||
<data name="arrow_right_blue" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="arrow_up_green" xml:space="preserve">
|
||||
<value />
|
||||
</data>
|
||||
<data name="textStartTimeMissing" xml:space="preserve">
|
||||
<value>If an end time is set, a start time is also required</value>
|
||||
</data>
|
||||
</root>
|
||||
@ -89,8 +89,7 @@
|
||||
<ColumnDefinition Width="30" />
|
||||
<ColumnDefinition Width=".5*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<xctk:WatermarkTextBox x:Name="textBoxSearch" Grid.Column="0" Margin="2" Watermark="{x:Static p:Resources.textEnterKeyword}" PreviewTextInput="textBoxSearch_PreviewTextInput"
|
||||
DataObject.Pasting="textBoxSearch_Pasting" TextChanged="textBoxSearch_TextChanged" />
|
||||
<xctk:WatermarkTextBox x:Name="textBoxSearch" Grid.Column="0" Margin="2" Watermark="{x:Static p:Resources.textEnterKeyword}" TextChanged="textBoxSearch_TextChanged" />
|
||||
<CheckBox x:Name="checkBoxOwn" VerticalAlignment="Center" Grid.Column="1" HorizontalAlignment="Right" Margin="2" Checked="checkBoxOwn_Checked" Unchecked="checkBoxOwn_Checked" />
|
||||
<Label Content="{x:Static p:Resources.textMineOnly}" Grid.Column="2" />
|
||||
</Grid>
|
||||
|
||||
@ -183,23 +183,11 @@ namespace BreCalClient
|
||||
foreach (Participant agency in comboBoxAgencies.SelectedItems)
|
||||
_model.Agencies.Add(agency.Id);
|
||||
SearchFilterChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void textBoxSearch_Pasting(object sender, System.Windows.DataObjectPastingEventArgs e)
|
||||
{
|
||||
this.SearchFilter.SearchString = e.SourceDataObject.ToString();
|
||||
SearchFilterChanged?.Invoke();
|
||||
}
|
||||
|
||||
private void textBoxSearch_PreviewTextInput(object sender, TextCompositionEventArgs e)
|
||||
{
|
||||
this.SearchFilter.SearchString = this.textBoxSearch.Text + e.Text;
|
||||
SearchFilterChanged?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private void textBoxSearch_TextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
this.SearchFilter.SearchString = this.textBoxSearch.Text;
|
||||
this.SearchFilter.SearchString = this.textBoxSearch.Text.Trim();
|
||||
SearchFilterChanged?.Invoke();
|
||||
}
|
||||
|
||||
|
||||
@ -51,7 +51,8 @@ def create_app(test_config=None, instance_path=None):
|
||||
try:
|
||||
import os
|
||||
print(f'Instance path = {app.instance_path}')
|
||||
os.makedirs(app.instance_path)
|
||||
if not os.path.exists(app.instance_path):
|
||||
os.makedirs(app.instance_path)
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
@ -6,9 +6,12 @@ import typing
|
||||
from BreCal.schemas.model import Shipcall, Ship, Participant, Berth, User, Times, ShipcallParticipantMap
|
||||
from BreCal.database.enums import ParticipantType
|
||||
from BreCal.local_db import getPoolConnection
|
||||
from BreCal.database.sql_queries import SQLQuery
|
||||
from BreCal.schemas import model
|
||||
|
||||
|
||||
def pandas_series_to_data_model():
|
||||
return
|
||||
return
|
||||
|
||||
def set_participant_type(x, participant_df)->int:
|
||||
"""
|
||||
@ -20,12 +23,12 @@ def set_participant_type(x, participant_df)->int:
|
||||
participant_id = x["participant_id"]
|
||||
participant_type = participant_df.loc[participant_id, "type"]
|
||||
return participant_type
|
||||
|
||||
|
||||
def get_synchronous_shipcall_times_standalone(query_time:pd.Timestamp, all_df_times:pd.DataFrame, delta_threshold=900)->int:
|
||||
"""
|
||||
This function counts all entries in {all_df_times}, which have the same timestamp as {query_time}.
|
||||
It does so by:
|
||||
1.) selecting all eta_berth & etd_berth entries
|
||||
1.) selecting all eta_berth & etd_berth entries
|
||||
2.) measuring the timedelta towards {query_time}
|
||||
3.) converting the timedelta to total absolute seconds (positive or negative time differences do not matter)
|
||||
4.) applying a {delta_threshold} to identify, whether two times are too closely together
|
||||
@ -57,8 +60,8 @@ def get_synchronous_shipcall_times_standalone(query_time:pd.Timestamp, all_df_ti
|
||||
|
||||
def execute_sql_query_standalone(query, param={}, pooledConnection=None, model=None, command_type="query"):
|
||||
"""
|
||||
execute an arbitrary query with a set of parameters, return the output and convert it to a list.
|
||||
when the pooled connection is rebuilt, it will be closed at the end of the function.
|
||||
execute an arbitrary query with a set of parameters, return the output and convert it to a list.
|
||||
when the pooled connection is rebuilt, it will be closed at the end of the function.
|
||||
"""
|
||||
rebuild_pooled_connection = pooledConnection is None
|
||||
|
||||
@ -89,18 +92,45 @@ def execute_sql_query_standalone(query, param={}, pooledConnection=None, model=N
|
||||
schemas = commands.query_single_or_default(query, sentinel, param=param) if model is None else commands.query_single_or_default(query, sentinel, param=param, model=model)
|
||||
if schemas is sentinel:
|
||||
raise Exception("no such record")
|
||||
elif command_type=="single_or_none":
|
||||
sentinel = object()
|
||||
|
||||
# pulls a *single* row from the query. Typically, these queries require an ID within the param dictionary.
|
||||
# when providing a model, such as model.Shipcall, the dataset is immediately translated into a data model.
|
||||
schemas = commands.query_single_or_default(query, sentinel, param=param) if model is None else commands.query_single_or_default(query, sentinel, param=param, model=model)
|
||||
schemas = None if schemas is sentinel else schemas
|
||||
|
||||
elif command_type=="execute_scalar":
|
||||
schemas = commands.execute_scalar(query)
|
||||
|
||||
else:
|
||||
raise ValueError(command_type)
|
||||
|
||||
|
||||
finally: # if needed, ensure that the pooled connection is closed.
|
||||
if rebuild_pooled_connection:
|
||||
pooledConnection.close()
|
||||
return schemas
|
||||
|
||||
|
||||
def get_assigned_participant_of_type(shipcall_id:int, participant_type:typing.Union[int,model.ParticipantType])->typing.Optional[model.Participant]:
|
||||
"""obtains the ShipcallParticipantMap of a given shipcall and finds the participant id of a desired type. Finally, returns the respective Participant"""
|
||||
spm_shipcall_data = execute_sql_query_standalone(
|
||||
query=SQLQuery.get_shipcall_participant_map_by_shipcall_id_and_type(),
|
||||
param={"id":shipcall_id, "type":int(participant_type)},
|
||||
command_type="query") # returns a list of matches
|
||||
|
||||
if len(spm_shipcall_data)==0:
|
||||
return None
|
||||
|
||||
query = 'SELECT * FROM participant WHERE id=?participant_id?'
|
||||
assigned_participant = execute_sql_query_standalone(
|
||||
query=query,
|
||||
param={"participant_id":spm_shipcall_data[0]["participant_id"]},
|
||||
model=model.Participant,
|
||||
command_type="single_or_none"
|
||||
) # returns a list of matches
|
||||
return assigned_participant
|
||||
|
||||
|
||||
class SQLHandler():
|
||||
"""
|
||||
An object that reads SQL queries from the sql_connection and stores it in pandas DataFrames. The object can read all available tables
|
||||
@ -131,7 +161,7 @@ class SQLHandler():
|
||||
schema = cursor.fetchall()
|
||||
all_schemas = [schem[0] for schem in schema]
|
||||
return all_schemas
|
||||
|
||||
|
||||
def build_str_to_model_dict(self):
|
||||
"""
|
||||
creates a simple dictionary, which maps a string to a data object
|
||||
@ -151,7 +181,7 @@ class SQLHandler():
|
||||
cursor.execute(f"DESCRIBE {table_name}")
|
||||
cols = cursor.fetchall()
|
||||
column_names = [col_name[0] for col_name in cols]
|
||||
|
||||
|
||||
# 2.) get the data tuples
|
||||
cursor.execute(f"SELECT * FROM {table_name}")
|
||||
data = cursor.fetchall()
|
||||
@ -162,14 +192,14 @@ class SQLHandler():
|
||||
# 4.) build a dataframe from the respective data models (which ensures the correct data type)
|
||||
df = self.build_df_from_data_and_name(data, table_name)
|
||||
return df
|
||||
|
||||
|
||||
def build_df_from_data_and_name(self, data, table_name):
|
||||
data_model = self.str_to_model_dict.get(table_name)
|
||||
if data_model is not None:
|
||||
df = pd.DataFrame([data_model(**dat) for dat in data], columns=list(data_model.__annotations__.keys()))
|
||||
else:
|
||||
df = pd.DataFrame([dat for dat in data])
|
||||
return df
|
||||
return df
|
||||
|
||||
def mysql_to_df(self, query, table_name):
|
||||
"""provide an arbitrary sql query that should be read from a mysql server {sql_connection}. returns a pandas DataFrame with the obtained data"""
|
||||
@ -192,7 +222,7 @@ class SQLHandler():
|
||||
if 'id' in df.columns:
|
||||
df = df.set_index('id', inplace=False) # avoid inplace updates, so the raw sql remains unchanged
|
||||
return df
|
||||
|
||||
|
||||
def read_all(self, all_schemas):
|
||||
# create a dictionary, which maps every mysql schema to pandas DataFrames
|
||||
self.df_dict = self.build_full_mysql_df_dict(all_schemas)
|
||||
@ -212,7 +242,7 @@ class SQLHandler():
|
||||
query = f"SELECT * FROM {schem}"
|
||||
mysql_df_dict[schem] = self.mysql_to_df(query, table_name=schem)
|
||||
return mysql_df_dict
|
||||
|
||||
|
||||
def initialize_shipcall_participant_list(self):
|
||||
"""
|
||||
iteratively applies the .get_participants method to each shipcall.
|
||||
@ -226,10 +256,10 @@ class SQLHandler():
|
||||
# if the shipcall_id exists, the list contains ids
|
||||
# otherwise, return a blank list
|
||||
df['participants'] = df.apply(
|
||||
lambda x: self.get_participants(x.name),
|
||||
lambda x: self.get_participants(x.name),
|
||||
axis=1)
|
||||
return
|
||||
|
||||
|
||||
def add_participant_type_to_map(self):
|
||||
"""
|
||||
applies a lambda function, where the 'type'-column in the shipcall_participant_map is updated by reading the
|
||||
@ -242,14 +272,14 @@ class SQLHandler():
|
||||
#spm.loc[:,"type"] = spm.loc[:].apply(lambda x: set_participant_type(x, participant_df=participant_df),axis=1)
|
||||
#self.df_dict["shipcall_participant_map"] = spm
|
||||
return
|
||||
|
||||
|
||||
def get_assigned_participants(self, shipcall)->pd.DataFrame:
|
||||
"""return each participant of a respective shipcall, filtered by the shipcall id"""
|
||||
# get the shipcall_participant_map
|
||||
spm = self.df_dict["shipcall_participant_map"]
|
||||
assigned_participants = spm.loc[spm["shipcall_id"]==shipcall.id]
|
||||
return assigned_participants
|
||||
|
||||
|
||||
def get_assigned_participants_by_type(self, assigned_participants:pd.DataFrame, participant_type:ParticipantType):
|
||||
"""filters a dataframe of assigned_participants by the provided type enumerator"""
|
||||
if isinstance(participant_type,int):
|
||||
@ -258,7 +288,7 @@ class SQLHandler():
|
||||
assigned_participants_of_type = assigned_participants.loc[[participant_type in ParticipantType(int(pt_)) for pt_ in list(assigned_participants["type"].values)]]
|
||||
#assigned_participants_of_type = assigned_participants.loc[assigned_participants["type"]==participant_type.value]
|
||||
return assigned_participants_of_type
|
||||
|
||||
|
||||
def check_if_any_participant_of_type_is_unassigned(self, shipcall, *args:list[ParticipantType])->bool:
|
||||
"""
|
||||
given a list of input arguments, where item is a participant type, the function determines, whether at least one participant
|
||||
@ -275,13 +305,13 @@ class SQLHandler():
|
||||
unassignment = len(assignments_of_type)==0 # a participant type does not exist, when there is no match
|
||||
unassigned.append(unassignment)
|
||||
return any(unassigned) # returns a single boolean, whether ANY of the types is not assigned
|
||||
|
||||
|
||||
def standardize_model_str(self, model_str:str)->str:
|
||||
"""check if the 'model_str' is valid and apply lowercasing to the string"""
|
||||
model_str = model_str.lower()
|
||||
assert model_str in list(self.df_dict.keys()), f"cannot find the requested 'model_str' in mysql: {model_str}"
|
||||
return model_str
|
||||
|
||||
|
||||
def get_data(self, id:int, model_str:str):
|
||||
"""
|
||||
obtains {id} from the respective mysql database and builds a data model from that.
|
||||
@ -293,11 +323,11 @@ class SQLHandler():
|
||||
returns a Shipcall object
|
||||
"""
|
||||
model_str = self.standardize_model_str(model_str)
|
||||
|
||||
|
||||
df = self.df_dict.get(model_str)
|
||||
data = self.df_loc_to_data_model(df, id, model_str)
|
||||
return data
|
||||
|
||||
|
||||
def get_all(self, model_str:str)->list:
|
||||
"""
|
||||
given a model string (e.g., 'shipcall'), return a list of all
|
||||
@ -311,13 +341,13 @@ class SQLHandler():
|
||||
for _aid in all_ids
|
||||
]
|
||||
return all_data
|
||||
|
||||
|
||||
def df_loc_to_data_model(self, df, id, model_str, loc_type:str="loc"):
|
||||
if not len(df)>0:
|
||||
import warnings
|
||||
warnings.warn(f"empty dataframe in SQLHandler.df_loc_to_data_model for model type: {model_str}\n")
|
||||
return df
|
||||
|
||||
|
||||
# get a pandas series from the dataframe
|
||||
series = df.loc[id] if loc_type=="loc" else df.iloc[id]
|
||||
|
||||
@ -330,7 +360,7 @@ class SQLHandler():
|
||||
data = {**{'id':int(id)}, **series.to_dict()} # 'id' must be added manually, as .to_dict does not contain the index, which was set with .set_index
|
||||
data = data_model(**data)
|
||||
return data
|
||||
|
||||
|
||||
def filter_df_by_participant_type(self, df, participant_type:typing.Union[int, ParticipantType])->pd.DataFrame:
|
||||
"""
|
||||
As ParticipantTypes are Flag objects, a dataframe's integer might resemble multiple participant types simultaneously.
|
||||
@ -346,7 +376,7 @@ class SQLHandler():
|
||||
participant_type = ParticipantType(participant_type)
|
||||
filtered_df = df.loc[[participant_type in ParticipantType(df_pt) for df_pt in list(df["participant_type"].values)]]
|
||||
return filtered_df
|
||||
|
||||
|
||||
def get_times_for_participant_type(self, df_times, participant_type:int):
|
||||
filtered_series = self.filter_df_by_participant_type(df_times, participant_type)
|
||||
#filtered_series = df_times.loc[df_times["participant_type"]==participant_type]
|
||||
@ -355,14 +385,14 @@ class SQLHandler():
|
||||
return None
|
||||
|
||||
if not len(filtered_series)<=1:
|
||||
# correcting the error: ERROR:root:found multiple results
|
||||
# correcting the error: ERROR:root:found multiple results
|
||||
# however, a warning will still be issued
|
||||
import warnings
|
||||
warnings.warn(f"found multiple results in function SQLHandler.get_times_for_participant_type\nConsidering only the first match!\nAffected Times Indexes: {filtered_series.index}")
|
||||
|
||||
times = self.df_loc_to_data_model(filtered_series, id=0, model_str='times', loc_type="iloc") # use iloc! to retrieve the first result
|
||||
return times
|
||||
|
||||
|
||||
def dataframe_to_data_model_list(self, df, model_str)->list:
|
||||
model_str = self.standardize_model_str(model_str)
|
||||
|
||||
@ -383,22 +413,22 @@ class SQLHandler():
|
||||
df = self.df_dict.get("shipcall_participant_map")
|
||||
if 'shipcall_id' in list(df.columns):
|
||||
df = df.set_index('shipcall_id', inplace=False)
|
||||
|
||||
|
||||
# the 'if' call is needed to ensure, that no Exception is raised, when the shipcall_id is not present in the df
|
||||
participant_id_list = df.loc[shipcall_id, "participant_id"].tolist() if shipcall_id in list(df.index) else []
|
||||
if not isinstance(participant_id_list,list):
|
||||
participant_id_list = [participant_id_list]
|
||||
return participant_id_list
|
||||
|
||||
|
||||
def get_times_of_shipcall(self, shipcall)->pd.DataFrame:
|
||||
df_times = self.df_dict.get('times') # -> pd.DataFrame
|
||||
df_times = df_times.loc[df_times["shipcall_id"]==shipcall.id]
|
||||
return df_times
|
||||
|
||||
|
||||
def get_times_for_agency(self, non_null_column=None)->pd.DataFrame:
|
||||
"""
|
||||
options:
|
||||
non_null_column:
|
||||
non_null_column:
|
||||
None or str. If provided, the 'non_null_column'-column of the dataframe will be filtered,
|
||||
so only entries with provided values are returned (filters all NaN and NaT entries)
|
||||
"""
|
||||
@ -407,8 +437,8 @@ class SQLHandler():
|
||||
|
||||
# filter out all NaN and NaT entries
|
||||
if non_null_column is not None:
|
||||
# in the Pandas documentation, it says for .isnull():
|
||||
# "This function takes a scalar or array-like object and indicates whether values are missing
|
||||
# in the Pandas documentation, it says for .isnull():
|
||||
# "This function takes a scalar or array-like object and indicates whether values are missing
|
||||
# (NaN in numeric arrays, None or NaN in object arrays, NaT in datetimelike)."
|
||||
df_times = df_times.loc[~df_times[non_null_column].isnull()] # NOT null filter
|
||||
|
||||
@ -416,10 +446,10 @@ class SQLHandler():
|
||||
times_agency = self.filter_df_by_participant_type(df_times, ParticipantType.AGENCY.value)
|
||||
#times_agency = df_times.loc[df_times["participant_type"]==ParticipantType.AGENCY.value]
|
||||
return times_agency
|
||||
|
||||
|
||||
def filter_df_by_key_value(self, df, key, value)->pd.DataFrame:
|
||||
return df.loc[df[key]==value]
|
||||
|
||||
|
||||
def get_unique_ship_counts(self, all_df_times:pd.DataFrame, times_agency:pd.DataFrame, query:str, rounding:str="min", maximum_threshold=3):
|
||||
"""given a dataframe of all agency times, get all unique ship counts, their values (datetime) and the string tags. returns a tuple (values,unique,counts)"""
|
||||
# #deprecated!
|
||||
|
||||
@ -12,3 +12,11 @@ def get_user_data_for_id(user_id:int, expiration_time:int=90):
|
||||
user_data["exp"] = (datetime.datetime.now()+datetime.timedelta(minutes=expiration_time)).timestamp()
|
||||
return user_data
|
||||
|
||||
|
||||
def get_times_data_for_id(times_id:int):
|
||||
"""helper function to load previous times data from the database"""
|
||||
query = "SELECT * FROM times where id = ?id?"
|
||||
pdata = execute_sql_query_standalone(query=query, param={"id":times_id})
|
||||
pdata = pdata[0] if len(pdata)>0 else None
|
||||
assert pdata is not None, f"could not find times with id {times_id}"
|
||||
return pdata
|
||||
@ -28,7 +28,7 @@ def validation_error_default_asserts(response):
|
||||
def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict):
|
||||
"""this function applies more complex validation functions to data, which is sent to a post-request of shipcalls"""
|
||||
# DEPRECATED: this function has been refactored into InputValidationShipcall (see methods for POST and PUT evaluation)
|
||||
# #TODO_refactor: this function is pretty complex. One may instead build an object, which calls the methods separately.
|
||||
# #TODO_refactor: this function is pretty complex. One may instead build an object, which calls the methods separately.
|
||||
|
||||
##### Section 1: check user_data #####
|
||||
# check, whether the user belongs to a participant, which is of type ParticipantType.BSMD
|
||||
@ -36,23 +36,23 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict
|
||||
is_bsmd = check_if_user_is_bsmd_type(user_data)
|
||||
if not is_bsmd:
|
||||
raise ValidationError({"user_participant_type":f"current user does not belong to BSMD. Cannot post shipcalls. Found user data: {user_data}"})
|
||||
|
||||
|
||||
##### Section 2: check loadedModel #####
|
||||
valid_ship_id = check_if_ship_id_is_valid(ship_id=loadedModel.get("ship_id", None))
|
||||
if not valid_ship_id:
|
||||
raise ValidationError({"ship_id":f"provided an invalid ship id, which is not found in the database: {loadedModel.get('ship_id', None)}"})
|
||||
|
||||
|
||||
valid_arrival_berth_id = check_if_berth_id_is_valid(berth_id=loadedModel.get("arrival_berth_id", None))
|
||||
if not valid_arrival_berth_id:
|
||||
raise ValidationError({"arrival_berth_id":f"provided an invalid arrival berth id, which is not found in the database: {loadedModel.get('arrival_berth_id', None)}"})
|
||||
|
||||
|
||||
valid_departure_berth_id = check_if_berth_id_is_valid(berth_id=loadedModel.get("departure_berth_id", None))
|
||||
if not valid_departure_berth_id:
|
||||
raise ValidationError({"departure_berth_id":f"provided an invalid departure berth id, which is not found in the database: {loadedModel.get('departure_berth_id', None)}"})
|
||||
|
||||
|
||||
valid_participant_ids = check_if_participant_ids_are_valid(participants=loadedModel.get("participants",[]))
|
||||
if not valid_participant_ids:
|
||||
raise ValidationError({"participants":f"one of the provided participant ids is invalid. Could not find one of these in the database: {loadedModel.get('participants', None)}"})
|
||||
raise ValidationError({"participants":f"one of the provided participant ids is invalid. Could not find one of these in the database: {loadedModel.get('participants', None)}"})
|
||||
|
||||
|
||||
##### Section 3: check content #####
|
||||
@ -63,18 +63,18 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict
|
||||
value = content.get(forbidden_key, None)
|
||||
if value is not None:
|
||||
raise ValidationError({"forbidden_key":f"'{forbidden_key}' may not be set on POST. Found: {value}"})
|
||||
|
||||
|
||||
voyage_str_is_invalid = check_if_string_has_special_characters(text=content.get("voyage",""))
|
||||
if voyage_str_is_invalid:
|
||||
raise ValidationError({"voyage":f"there are invalid characters in the 'voyage'-string. Please use only digits and ASCII letters. Allowed: {ascii_letters+digits}. Found: {content.get('voyage')}"})
|
||||
|
||||
raise ValidationError({"voyage":f"there are invalid characters in the 'voyage'-string. Please use only digits and ASCII letters. Allowed: {ascii_letters+digits}. Found: {content.get('voyage')}"})
|
||||
|
||||
|
||||
##### Section 4: check loadedModel & content #####
|
||||
# #TODO_refactor: these methods should be placed in separate locations
|
||||
|
||||
# existance checks in content
|
||||
# datetime checks in loadedModel (datetime.datetime objects). Dates should be in the future.
|
||||
time_now = datetime.datetime.now()
|
||||
# datetime checks in loadedModel (datetime.datetime objects). Dates should be in the future.
|
||||
time_now = datetime.datetime.now() - datetime.timedelta(days=1)
|
||||
type_ = loadedModel.get("type", int(ShipcallType.undefined))
|
||||
if int(type_)==int(ShipcallType.undefined):
|
||||
raise ValidationError({"type":f"providing 'type' is mandatory. Missing key!"})
|
||||
@ -85,7 +85,7 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict
|
||||
if content.get("arrival_berth_id", None) is None:
|
||||
raise ValidationError({"arrival_berth_id":f"providing 'arrival_berth_id' is mandatory. Missing key!"})
|
||||
if not eta >= time_now:
|
||||
raise ValidationError({"eta":f"'eta' must be in the future. Incorrect datetime provided."})
|
||||
raise ValidationError({"eta":f"'eta' is too far in the past. Incorrect datetime provided."})
|
||||
elif int(type_)==int(ShipcallType.departure):
|
||||
etd = loadedModel.get("etd")
|
||||
if (content.get("etd", None) is None):
|
||||
@ -93,7 +93,7 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict
|
||||
if content.get("departure_berth_id", None) is None:
|
||||
raise ValidationError({"departure_berth_id":f"providing 'departure_berth_id' is mandatory. Missing key!"})
|
||||
if not etd >= time_now:
|
||||
raise ValidationError({"etd":f"'etd' must be in the future. Incorrect datetime provided."})
|
||||
raise ValidationError({"etd":f"'etd' is too far in the past. Incorrect datetime provided."})
|
||||
elif int(type_)==int(ShipcallType.shifting):
|
||||
eta = loadedModel.get("eta")
|
||||
etd = loadedModel.get("etd")
|
||||
@ -103,20 +103,20 @@ def validate_posted_shipcall_data(user_data:dict, loadedModel:dict, content:dict
|
||||
if (content.get("arrival_berth_id", None) is None) or (content.get("departure_berth_id", None) is None):
|
||||
raise ValidationError({"arrival_berth_id_or_departure_berth_id":f"providing 'arrival_berth_id' & 'departure_berth_id' is mandatory. Missing key!"})
|
||||
if (not eta >= time_now) or (not etd >= time_now) or (not eta >= etd):
|
||||
raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must be in the future. Incorrect datetime provided."})
|
||||
|
||||
raise ValidationError({"eta_or_etd":f"'eta' and 'etd' are too far in the past. Incorrect datetime provided."})
|
||||
|
||||
tidal_window_from = loadedModel.get("tidal_window_from", None)
|
||||
tidal_window_to = loadedModel.get("tidal_window_to", None)
|
||||
if tidal_window_to is not None:
|
||||
if not tidal_window_to >= time_now:
|
||||
raise ValidationError({"tidal_window_to":f"'tidal_window_to' must be in the future. Incorrect datetime provided."})
|
||||
raise ValidationError({"tidal_window_to":f"'tidal_window_to' is too far in the past. Incorrect datetime provided."})
|
||||
|
||||
if tidal_window_from is not None:
|
||||
if not tidal_window_from >= time_now:
|
||||
raise ValidationError({"tidal_window_from":f"'tidal_window_from' must be in the future. Incorrect datetime provided."})
|
||||
|
||||
raise ValidationError({"tidal_window_from":f"'tidal_window_from' is too far in the past. Incorrect datetime provided."})
|
||||
|
||||
# #TODO: assert tidal_window_from > tidal_window_to
|
||||
|
||||
|
||||
# #TODO: len of participants > 0, if agency
|
||||
# * assigned participant for agency
|
||||
return
|
||||
@ -126,21 +126,21 @@ class InputValidation():
|
||||
def __init__(self):
|
||||
self.build_supported_models_dictionary()
|
||||
return
|
||||
|
||||
|
||||
def build_supported_models_dictionary(self):
|
||||
self.supported_models = {
|
||||
Ship:ShipValidation(),
|
||||
Shipcall:ShipcallValidation(),
|
||||
Berth:BerthValidation(),
|
||||
User:UserValidation(),
|
||||
Berth:BerthValidation(),
|
||||
User:UserValidation(),
|
||||
Participant:ParticipantValidation(),
|
||||
}
|
||||
return
|
||||
|
||||
|
||||
def assert_if_not_supported(self, dataclass_object):
|
||||
assert type(dataclass_object) in self.supported_models, f"unsupported model. Found: {type(dataclass_object)}"
|
||||
return
|
||||
|
||||
|
||||
def verify(self, dataclass_object):
|
||||
self.assert_if_not_supported(dataclass_object)
|
||||
|
||||
@ -157,17 +157,17 @@ class DataclassValidation(ABC):
|
||||
"""parent class of dataclas validators, which determines the outline of every object"""
|
||||
def __init__(self):
|
||||
return
|
||||
|
||||
|
||||
def check(self, dataclass_object) -> (list, bool):
|
||||
"""
|
||||
the 'check' method provides a default style, how each dataclass object is validated. It returns a list of violations
|
||||
the 'check' method provides a default style, how each dataclass object is validated. It returns a list of violations
|
||||
and a boolean, which determines, whether the check is passed successfully
|
||||
"""
|
||||
all_rules = self.apply_all_rules(dataclass_object)
|
||||
violations = self.filter_violations(all_rules)
|
||||
input_validation_state = self.evaluate(violations)
|
||||
return (violations, input_validation_state)
|
||||
|
||||
|
||||
@abstractmethod
|
||||
def apply_all_rules(self, dataclass_object) -> list:
|
||||
"""
|
||||
@ -176,13 +176,13 @@ class DataclassValidation(ABC):
|
||||
"""
|
||||
all_rules = [(True, 'blank_validation_rule')]
|
||||
return all_rules
|
||||
|
||||
|
||||
def filter_violations(self, all_rules):
|
||||
"""input: all_rules, a list of tuples, where each element is (output, validation_name), which are (bool, str). """
|
||||
# if output is False, a violation is observed
|
||||
violations = [result[1] for result in all_rules if not result[0]]
|
||||
return violations
|
||||
|
||||
|
||||
def evaluate(self, violations) -> bool:
|
||||
input_validation_state = len(violations)==0
|
||||
return input_validation_state
|
||||
@ -194,7 +194,7 @@ class ShipcallValidation(DataclassValidation):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
return
|
||||
|
||||
|
||||
def apply_all_rules(self, dataclass_object) -> list:
|
||||
"""apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)"""
|
||||
raise NotImplementedError()
|
||||
@ -202,16 +202,16 @@ class ShipcallValidation(DataclassValidation):
|
||||
|
||||
|
||||
from BreCal.validators.schema_validation import ship_bollard_pull_is_defined_or_is_not_tug, ship_bollard_pull_is_none_or_in_range, ship_callsign_len_is_seven_at_maximum, ship_eni_len_is_eight, ship_imo_len_is_seven, ship_length_in_range, ship_participant_id_is_defined_or_is_not_tug, ship_participant_id_is_none_or_int, ship_width_in_range
|
||||
# skip: ship_max_draft_is_defined_or_is_not_tug, ship_max_draft_is_none_or_in_range,
|
||||
# skip: ship_max_draft_is_defined_or_is_not_tug, ship_max_draft_is_none_or_in_range,
|
||||
class ShipValidation(DataclassValidation):
|
||||
"""an object that validates a Ship dataclass object"""
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
return
|
||||
|
||||
|
||||
def apply_all_rules(self, dataclass_object) -> list:
|
||||
"""apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)"""
|
||||
# skip: ship_max_draft_is_defined_or_is_not_tug, ship_max_draft_is_none_or_in_range,
|
||||
# skip: ship_max_draft_is_defined_or_is_not_tug, ship_max_draft_is_none_or_in_range,
|
||||
"""
|
||||
#TODO_ship_max_draft
|
||||
with pytest.raises(AttributeError, match="'Ship' object has no attribute 'max_draft'"):
|
||||
@ -225,14 +225,14 @@ class ShipValidation(DataclassValidation):
|
||||
check_rule(dataclass_object)
|
||||
|
||||
for check_rule in [
|
||||
ship_bollard_pull_is_defined_or_is_not_tug,
|
||||
ship_bollard_pull_is_none_or_in_range,
|
||||
ship_callsign_len_is_seven_at_maximum,
|
||||
ship_eni_len_is_eight,
|
||||
ship_imo_len_is_seven,
|
||||
ship_length_in_range,
|
||||
ship_participant_id_is_defined_or_is_not_tug,
|
||||
ship_participant_id_is_none_or_int,
|
||||
ship_bollard_pull_is_defined_or_is_not_tug,
|
||||
ship_bollard_pull_is_none_or_in_range,
|
||||
ship_callsign_len_is_seven_at_maximum,
|
||||
ship_eni_len_is_eight,
|
||||
ship_imo_len_is_seven,
|
||||
ship_length_in_range,
|
||||
ship_participant_id_is_defined_or_is_not_tug,
|
||||
ship_participant_id_is_none_or_int,
|
||||
ship_width_in_range
|
||||
]
|
||||
]
|
||||
@ -243,7 +243,7 @@ class BerthValidation(DataclassValidation):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
return
|
||||
|
||||
|
||||
def apply_all_rules(self, dataclass_object) -> list:
|
||||
"""apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)"""
|
||||
raise NotImplementedError()
|
||||
@ -254,7 +254,7 @@ class UserValidation(DataclassValidation):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
return
|
||||
|
||||
|
||||
def apply_all_rules(self, dataclass_object) -> list:
|
||||
"""apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)"""
|
||||
raise NotImplementedError()
|
||||
@ -266,7 +266,7 @@ class ParticipantValidation(DataclassValidation):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
return
|
||||
|
||||
|
||||
def apply_all_rules(self, dataclass_object) -> list:
|
||||
"""apply all input validation rules to determine, whether there are violations. returns a list of tuples (output, validation_name)"""
|
||||
|
||||
|
||||
@ -6,7 +6,7 @@ from marshmallow import ValidationError
|
||||
from string import ascii_letters, digits
|
||||
|
||||
from BreCal.schemas.model import Ship, Shipcall, Berth, User, Participant, ShipcallType
|
||||
from BreCal.database.sql_handler import execute_sql_query_standalone
|
||||
from BreCal.database.sql_handler import execute_sql_query_standalone
|
||||
from BreCal.database.sql_queries import SQLQuery
|
||||
from BreCal.impl.participant import GetParticipant
|
||||
from BreCal.impl.ships import GetShips
|
||||
@ -27,7 +27,7 @@ class InputValidationShip():
|
||||
Example:
|
||||
InputValidationShip.evaluate(user_data, loadedModel, content)
|
||||
|
||||
When the data violates one of the rules, a marshmallow.ValidationError is raised, which details the issues.
|
||||
When the data violates one of the rules, a marshmallow.ValidationError is raised, which details the issues.
|
||||
"""
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
@ -37,13 +37,13 @@ class InputValidationShip():
|
||||
# 1.) Only users of type BSMD are allowed to POST
|
||||
InputValidationShip.check_user_is_bsmd_type(user_data)
|
||||
|
||||
# 2.) The ship IMOs are used as matching keys. They must be unique in the database.
|
||||
# 2.) The ship IMOs are used as matching keys. They must be unique in the database.
|
||||
InputValidationShip.check_ship_imo_already_exists(loadedModel)
|
||||
|
||||
# 3.) Check for reasonable Values (see BreCal.schemas.model.ShipSchema)
|
||||
InputValidationShip.optionally_evaluate_bollard_pull_value(content)
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def evaluate_put_data(user_data:dict, loadedModel:dict, content:dict):
|
||||
# 1.) Only users of type BSMD are allowed to PUT
|
||||
@ -58,20 +58,20 @@ class InputValidationShip():
|
||||
# 4.) Check for reasonable Values (see BreCal.schemas.model.ShipSchema)
|
||||
InputValidationShip.optionally_evaluate_bollard_pull_value(content)
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def evaluate_delete_data(user_data:dict, ship_id:typing.Optional[int]):
|
||||
if ship_id is None:
|
||||
raise ValidationError({"id":f"The ship id must be provided."})
|
||||
ship_id = int(ship_id)
|
||||
|
||||
|
||||
# 1.) Only users of type BSMD are allowed to PUT
|
||||
InputValidationShip.check_user_is_bsmd_type(user_data)
|
||||
|
||||
# 2.) The dataset entry may not be deleted already
|
||||
InputValidationShip.check_if_entry_is_already_deleted(ship_id)
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def optionally_evaluate_bollard_pull_value(content:dict):
|
||||
bollard_pull = content.get("bollard_pull",None)
|
||||
@ -89,7 +89,7 @@ class InputValidationShip():
|
||||
is_bsmd = check_if_user_is_bsmd_type(user_data)
|
||||
if not is_bsmd:
|
||||
raise ValidationError({"participant_type":f"current user does not belong to BSMD. Cannot post, put or delete ships. Found user data: {user_data}"})
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_ship_imo_already_exists(loadedModel:dict):
|
||||
# get the ships, convert them to a list of JSON dictionaries
|
||||
@ -97,35 +97,35 @@ class InputValidationShip():
|
||||
ships = json.loads(response)
|
||||
|
||||
# extract only the 'imo' values
|
||||
ship_imos = [ship.get("imo") for ship in ships]
|
||||
ship_imos = [ship.get("imo") for ship in ships if not ship.get("deleted")]
|
||||
|
||||
# check, if the imo in the POST-request already exists in the list
|
||||
imo_already_exists = loadedModel.get("imo") in ship_imos
|
||||
if imo_already_exists:
|
||||
raise ValidationError({"imo":f"the provided ship IMO {loadedModel.get('imo')} already exists. A ship may only be added, if there is no other ship with the same IMO number."})
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def put_content_may_not_contain_imo_number(content:dict):
|
||||
# IMO is a required field, so it will never be None outside of tests. If so, there is no violation
|
||||
put_data_ship_imo = content.get("imo",None)
|
||||
if put_data_ship_imo is None:
|
||||
return
|
||||
|
||||
|
||||
# grab the ship by its ID and compare, whether the IMO is unchanged
|
||||
ship = execute_sql_query_standalone(SQLQuery.get_ship_by_id(), param={"id":content.get("id")}, command_type="single", model=Ship)
|
||||
|
||||
if put_data_ship_imo != ship.imo:
|
||||
raise ValidationError({"imo":f"The IMO number field may not be changed since it serves the purpose of a primary (matching) key."})
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def content_contains_ship_id(content:dict):
|
||||
put_data_ship_id = content.get('id',None)
|
||||
if put_data_ship_id is None:
|
||||
raise ValidationError({"id":f"The id field is required."})
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_if_entry_is_already_deleted(ship_id:typing.Optional[int]):
|
||||
"""
|
||||
@ -147,4 +147,4 @@ class InputValidationShip():
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ from BreCal.impl.berths import GetBerths
|
||||
|
||||
from BreCal.database.enums import ParticipantType, ParticipantFlag
|
||||
from BreCal.validators.input_validation_utils import check_if_user_is_bsmd_type, check_if_ship_id_is_valid, check_if_berth_id_is_valid, check_if_participant_ids_are_valid, check_if_participant_ids_and_types_are_valid, get_shipcall_id_dictionary, get_participant_type_from_user_data
|
||||
from BreCal.database.sql_handler import get_assigned_participant_of_type
|
||||
from BreCal.database.sql_handler import execute_sql_query_standalone
|
||||
from BreCal.validators.validation_base_utils import check_if_int_is_valid_flag
|
||||
from BreCal.validators.validation_base_utils import check_if_string_has_special_characters
|
||||
@ -28,7 +29,7 @@ class InputValidationShipcall():
|
||||
Example:
|
||||
InputValidationShipcall.evaluate(user_data, loadedModel, content)
|
||||
|
||||
When the data violates one of the rules, a marshmallow.ValidationError is raised, which details the issues.
|
||||
When the data violates one of the rules, a marshmallow.ValidationError is raised, which details the issues.
|
||||
|
||||
"""
|
||||
def __init__(self) -> None:
|
||||
@ -38,7 +39,7 @@ class InputValidationShipcall():
|
||||
def evaluate_post_data(user_data:dict, loadedModel:dict, content:dict):
|
||||
"""
|
||||
this function combines multiple validation functions to verify data, which is sent to the API as a shipcall's POST-request
|
||||
|
||||
|
||||
checks:
|
||||
1. permission: only participants that belong to the BSMD or AGENCY groups are allowed to POST shipcalls
|
||||
2. reference checks: all refered objects within the Shipcall must exist
|
||||
@ -60,16 +61,16 @@ class InputValidationShipcall():
|
||||
# check for reasonable values in the shipcall fields
|
||||
InputValidationShipcall.check_shipcall_values(loadedModel, content, forbidden_keys=["evaluation", "evaluation_message"]) # "canceled"
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def evaluate_put_data(user_data:dict, loadedModel:dict, content:dict):
|
||||
"""
|
||||
this function combines multiple validation functions to verify data, which is sent to the API as a shipcall's PUT-request
|
||||
|
||||
|
||||
checks:
|
||||
1. user's authority:
|
||||
a) whether the user's participant is assigned to the shipcall (via shipcall-participant-map)
|
||||
b) whether the user is either an AGENCY (assigned) or the BSMD, in case the AGENCY allows the BSMD to edit their shipcalls
|
||||
b) whether the user is either an AGENCY (assigned) or the BSMD, in case the AGENCY allows the BSMD to edit their shipcalls
|
||||
2. existance of required fields
|
||||
3. all value-rules of the POST evaluation
|
||||
4. a canceled shipcall may not be changed
|
||||
@ -89,24 +90,24 @@ class InputValidationShipcall():
|
||||
# check the referenced IDs
|
||||
InputValidationShipcall.check_referenced_ids(loadedModel)
|
||||
|
||||
# check for reasonable values in the shipcall fields and checks for forbidden keys.
|
||||
# check for reasonable values in the shipcall fields and checks for forbidden keys.
|
||||
InputValidationShipcall.check_shipcall_values(loadedModel, content, forbidden_keys=["evaluation", "evaluation_message"], is_put_data=True)
|
||||
|
||||
# a canceled shipcall cannot be selected
|
||||
# Note: 'canceled' is allowed in PUT-requests, if it is not already set (which is checked by InputValidationShipcall.check_shipcall_is_cancel)
|
||||
InputValidationShipcall.check_shipcall_is_canceled(loadedModel, content)
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_shipcall_values(loadedModel:dict, content:dict, forbidden_keys:list=["evaluation", "evaluation_message"], is_put_data:bool=False):
|
||||
"""
|
||||
individually checks each value provided in the loadedModel/content.
|
||||
individually checks each value provided in the loadedModel/content.
|
||||
This function validates, whether the values are reasonable.
|
||||
|
||||
Also, some data may not be set in a POST-request.
|
||||
|
||||
options:
|
||||
is_put_data: bool. Some validation rules do not apply to POST data, but apply to PUT data. This flag separates the two.
|
||||
is_put_data: bool. Some validation rules do not apply to POST data, but apply to PUT data. This flag separates the two.
|
||||
"""
|
||||
# Note: BreCal.schemas.model.ShipcallSchema has an internal validation, which the marshmallow library provides. This is used
|
||||
# to verify values individually, when the schema is loaded with data.
|
||||
@ -116,24 +117,23 @@ class InputValidationShipcall():
|
||||
# voyage shall not contain special characters
|
||||
voyage_str_is_invalid = check_if_string_has_special_characters(text=content.get("voyage",""))
|
||||
if voyage_str_is_invalid:
|
||||
raise ValidationError({"voyage":f"there are invalid characters in the 'voyage'-string. Please use only digits and ASCII letters. Allowed: {ascii_letters+digits}. Found: {content.get('voyage')}"})
|
||||
|
||||
raise ValidationError({"voyage":f"there are invalid characters in the 'voyage'-string. Please use only digits and ASCII letters. Allowed: {ascii_letters+digits}. Found: {content.get('voyage')}"})
|
||||
|
||||
# the 'flags' integer must be valid
|
||||
flags_value = content.get("flags", 0)
|
||||
if check_if_int_is_valid_flag(flags_value, enum_object=ParticipantFlag):
|
||||
raise ValidationError({"flags":f"incorrect value provided for 'flags'. Must be a valid combination of the flags."})
|
||||
|
||||
|
||||
if is_put_data:
|
||||
# the type of a shipcall may not be changed. It can only be set with the initial POST-request.
|
||||
InputValidationShipcall.check_shipcall_type_is_unchanged(loadedModel)
|
||||
else:
|
||||
# time values must use future-dates
|
||||
InputValidationShipcall.check_times_are_in_future(loadedModel, content)
|
||||
|
||||
InputValidationShipcall.check_times_are_in_future(loadedModel, content)
|
||||
|
||||
# some arguments must not be provided
|
||||
InputValidationShipcall.check_forbidden_arguments(content, forbidden_keys=forbidden_keys)
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_agency_in_shipcall_participant_map(user_data:dict, loadedModel:dict, content:dict, spm_shipcall_data:typing.Optional[list]=None):
|
||||
"""
|
||||
@ -144,11 +144,11 @@ class InputValidationShipcall():
|
||||
Upon violation, this method issues 'Forbidden'-Exceptions with HTTP status code 403. There are four reasons for violations:
|
||||
a) an agency tries to self-assign for a shipcall
|
||||
b) there is no assigned agency for the current shipcall
|
||||
c) an agency is assigned, but the current agency-user belongs to a different participant_id
|
||||
c) an agency is assigned, but the current agency-user belongs to a different participant_id
|
||||
d) the user must be of ParticipantType BSMD or AGENCY
|
||||
|
||||
args:
|
||||
spm_shipcall_data:
|
||||
spm_shipcall_data:
|
||||
a list of entries obtained from the ShipcallParticipantMap. These are deserialized dictionaries.
|
||||
e.g., [{'participant_id': 136, 'type': 8}, ]
|
||||
"""
|
||||
@ -157,11 +157,11 @@ class InputValidationShipcall():
|
||||
# read the ShipcallParticipantMap entry of the current shipcall_id. This is used within the input validation of a PUT request
|
||||
spm_shipcall_data = execute_sql_query_standalone(
|
||||
# #TODO_refactor: place this within the SQLQuery object
|
||||
query = "SELECT participant_id, type FROM shipcall_participant_map WHERE shipcall_id=?shipcall_id?",
|
||||
query = "SELECT participant_id, type FROM shipcall_participant_map WHERE shipcall_id=?shipcall_id?",
|
||||
param={"shipcall_id":loadedModel["id"]},
|
||||
pooledConnection=None
|
||||
)
|
||||
|
||||
|
||||
# which role should be set by the PUT request? If the agency is about to be set, an error will be created
|
||||
# read the user data from the JWT token (set when login is performed)
|
||||
user_type = get_participant_type_from_user_data(user_data) # decode JWT -> get 'type' value (guarantees to convert user type into an IntFlag)
|
||||
@ -179,7 +179,7 @@ class InputValidationShipcall():
|
||||
# user not AGENCY or BSMD
|
||||
raise werkzeug.exceptions.Forbidden(f"PUT Requests for shipcalls can only be issued by AGENCY or BSMD users.") # Forbidden: 403
|
||||
|
||||
# Placeholder: when a user is an AGENCY,
|
||||
# Placeholder: when a user is an AGENCY,
|
||||
|
||||
if (ParticipantType.AGENCY in user_type) & (any_type_is_agency):
|
||||
# self-assignment: agency sets agency participant
|
||||
@ -188,7 +188,7 @@ class InputValidationShipcall():
|
||||
if len(agency_entries)>0:
|
||||
# agency participant exists: participant id must be the same as shipcall participant map entry
|
||||
matching_spm_entry = [spm_entry for spm_entry in spm_shipcall_data if (spm_entry.get("participant_id")==user_data["id"]) & (int(spm_entry.get("type"))==int(ParticipantType.AGENCY))]
|
||||
|
||||
|
||||
if len(matching_spm_entry)==0:
|
||||
# An AGENCY was found, but a different participant_id is assigned to that AGENCY
|
||||
raise werkzeug.exceptions.Forbidden(f"A different participant_id is assigned as the AGENCY of this shipcall. Provided ID: {user_data.get('id')}, Assigned ShipcallParticipantMap: {agency_entries}") # Forbidden: 403
|
||||
@ -200,7 +200,7 @@ class InputValidationShipcall():
|
||||
# agency participant does not exist: there is no assigned agency role for the shipcall {shipcall_id}
|
||||
raise werkzeug.exceptions.Forbidden(f"There is no assigned agency for this shipcall. Shipcall ID: {loadedModel['id']}") # Forbidden: 403
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_user_is_bsmd_or_agent_type(user_data):
|
||||
"""
|
||||
@ -225,7 +225,7 @@ class InputValidationShipcall():
|
||||
check, whether the referenced entries exist (e.g., when a Ship ID is referenced, but does not exist, the validation fails)
|
||||
"""
|
||||
# #TODO: arrival and departure berth id should be coupled with the shipcall type. One shall not provide
|
||||
# arrival berth id when the shipcall type is departure or vise versa.
|
||||
# arrival berth id when the shipcall type is departure or vise versa.
|
||||
# a similar logic has already been implemented to the eta/etd or for the operation windows
|
||||
|
||||
# get all IDs from the loadedModel
|
||||
@ -237,23 +237,23 @@ class InputValidationShipcall():
|
||||
valid_ship_id = check_if_ship_id_is_valid(ship_id=ship_id)
|
||||
if not valid_ship_id:
|
||||
raise ValidationError({"ship_id":f"provided an invalid ship id, which is not found in the database: {ship_id}"})
|
||||
|
||||
|
||||
valid_arrival_berth_id = check_if_berth_id_is_valid(berth_id=arrival_berth_id)
|
||||
if not valid_arrival_berth_id:
|
||||
raise ValidationError({"arrival_berth_id":f"provided an invalid arrival berth id, which is not found in the database: {arrival_berth_id}"})
|
||||
|
||||
|
||||
valid_departure_berth_id = check_if_berth_id_is_valid(berth_id=departure_berth_id)
|
||||
if not valid_departure_berth_id:
|
||||
raise ValidationError({"departure_berth_id":f"provided an invalid departure berth id, which is not found in the database: {departure_berth_id}"})
|
||||
|
||||
|
||||
valid_participant_ids = check_if_participant_ids_are_valid(participants=participants)
|
||||
if not valid_participant_ids:
|
||||
raise ValidationError({"participants":f"one of the provided participant ids is invalid. Could not find one of these in the database: {participants}"})
|
||||
|
||||
|
||||
valid_participant_types = check_if_participant_ids_and_types_are_valid(participants=participants)
|
||||
if not valid_participant_types: # #TODO: according to Daniel, there may eventually be multi-assignment of participants for the same role
|
||||
raise ValidationError({"participants":f"every participant id and type should be listed only once. Found multiple entries for one of the participants."})
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_shipcall_type_is_unchanged(loadedModel:dict):
|
||||
# the type of a shipcall may only be set on POST requests. Afterwards, shipcall types may not be changed.
|
||||
@ -263,20 +263,20 @@ class InputValidationShipcall():
|
||||
if int(loadedModel["type"]) != int(shipcall.type):
|
||||
raise ValidationError({"type":f"The shipcall type may only be set in the initial POST-request. Afterwards, changing the shipcall type is not allowed."}) # @pytest.raises
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_forbidden_arguments(content:dict, forbidden_keys=["evaluation", "evaluation_message"]):
|
||||
"""
|
||||
a post-request must not contain the arguments 'canceled', 'evaluation', 'evaluation_message'.
|
||||
a put-request must not contain the arguments 'evaluation', 'evaluation_message'
|
||||
|
||||
|
||||
"""
|
||||
# the following keys should not be set in a POST-request.
|
||||
for forbidden_key in forbidden_keys:
|
||||
value = content.get(forbidden_key, None)
|
||||
if value is not None:
|
||||
raise ValidationError({"forbidden_key":f"'{forbidden_key}' may not be set on POST. Found: {value}"})
|
||||
return
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def check_required_fields_exist_based_on_type(loadedModel:dict, content:dict):
|
||||
@ -295,30 +295,30 @@ class InputValidationShipcall():
|
||||
|
||||
if int(type_)==int(ShipcallType.undefined):
|
||||
raise ValidationError({"type":f"providing 'type' is mandatory. Missing key!"})
|
||||
|
||||
|
||||
# arrival: arrival_berth_id & eta must exist
|
||||
elif int(type_)==int(ShipcallType.arrival):
|
||||
if eta is None:
|
||||
raise ValidationError({"eta":f"providing 'eta' is mandatory. Missing key!"})
|
||||
|
||||
|
||||
if arrival_berth_id is None:
|
||||
raise ValidationError({"arrival_berth_id":f"providing 'arrival_berth_id' is mandatory. Missing key!"})
|
||||
|
||||
|
||||
# departure: departive_berth_id and etd must exist
|
||||
elif int(type_)==int(ShipcallType.departure):
|
||||
if etd is None:
|
||||
raise ValidationError({"etd":f"providing 'etd' is mandatory. Missing key!"})
|
||||
|
||||
|
||||
if departure_berth_id is None:
|
||||
raise ValidationError({"departure_berth_id":f"providing 'departure_berth_id' is mandatory. Missing key!"})
|
||||
|
||||
|
||||
# shifting: arrival_berth_id, departure_berth_id, eta and etd must exist
|
||||
elif int(type_)==int(ShipcallType.shifting):
|
||||
if (eta is None) or (etd is None):
|
||||
raise ValidationError({"eta_or_etd":f"providing 'eta' and 'etd' is mandatory. Missing one of those keys!"})
|
||||
if (arrival_berth_id is None) or (departure_berth_id is None):
|
||||
raise ValidationError({"arrival_berth_id_or_departure_berth_id":f"providing 'arrival_berth_id' & 'departure_berth_id' is mandatory. Missing key!"})
|
||||
|
||||
|
||||
else:
|
||||
raise ValidationError({"type":f"incorrect 'type' provided!"})
|
||||
return
|
||||
@ -327,10 +327,10 @@ class InputValidationShipcall():
|
||||
def check_times_are_in_future(loadedModel:dict, content:dict):
|
||||
"""
|
||||
Dates should be in the future. Depending on the ShipcallType, specific values should be checked
|
||||
Perfornms datetime checks in the loadedModel (datetime.datetime objects).
|
||||
Performs datetime checks in the loadedModel (datetime.datetime objects).
|
||||
"""
|
||||
# obtain the current datetime to check, whether the provided values are in the future
|
||||
time_now = datetime.datetime.now()
|
||||
# obtain the current datetime to check, whether the provided values are after ref time
|
||||
time_ref = datetime.datetime.now() - datetime.timedelta(days=1)
|
||||
|
||||
type_ = loadedModel.get("type", ShipcallType.undefined.name)
|
||||
if isinstance(type_, str): # convert the name string to a ShipcallType data model
|
||||
@ -348,14 +348,14 @@ class InputValidationShipcall():
|
||||
tidal_window_to = loadedModel.get("tidal_window_to", None)
|
||||
|
||||
# Estimated arrival or departure times
|
||||
InputValidationShipcall.check_times_in_future_based_on_type(type_, time_now, eta, etd)
|
||||
|
||||
InputValidationShipcall.check_times_in_future_based_on_type(type_, time_ref, eta, etd)
|
||||
|
||||
# Tidal Window
|
||||
InputValidationShipcall.check_tidal_window_in_future(type_, time_now, tidal_window_from, tidal_window_to)
|
||||
InputValidationShipcall.check_tidal_window_in_future(type_, time_ref, tidal_window_from, tidal_window_to)
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_times_in_future_based_on_type(type_, time_now, eta, etd):
|
||||
def check_times_in_future_based_on_type(type_, time_ref, eta, etd):
|
||||
"""
|
||||
checks, whether the ETA & ETD times are in the future.
|
||||
based on the type, this function checks:
|
||||
@ -365,7 +365,9 @@ class InputValidationShipcall():
|
||||
"""
|
||||
if (eta is None) and (etd is None):
|
||||
return
|
||||
|
||||
|
||||
time_in_a_year = datetime.datetime.now().replace(datetime.datetime.now().year + 1)
|
||||
|
||||
if type_ is None:
|
||||
raise ValidationError({"type":f"when providing 'eta' or 'etd', one must provide the type of the shipcall, so the datetimes can be verified."})
|
||||
|
||||
@ -379,20 +381,24 @@ class InputValidationShipcall():
|
||||
if eta is None: # null values -> no violation
|
||||
return
|
||||
|
||||
if not eta > time_now:
|
||||
raise ValidationError({"eta":f"'eta' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETA: {eta}."})
|
||||
if not eta > time_ref:
|
||||
raise ValidationError({"eta":f"'eta' is too far in the past. Incorrect datetime provided. Current Time: {time_ref}. ETA: {eta}."})
|
||||
if etd is not None:
|
||||
raise ValidationError({"etd":f"'etd' should not be set when the shipcall type is 'arrival'."})
|
||||
if eta > time_in_a_year:
|
||||
raise ValidationError({"eta":f"'eta' is more than a year in the future. ETA: {eta}."})
|
||||
|
||||
elif int(type_)==int(ShipcallType.departure):
|
||||
if etd is None: # null values -> no violation
|
||||
return
|
||||
|
||||
if not etd > time_now:
|
||||
raise ValidationError({"etd":f"'etd' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETD: {etd}."})
|
||||
if not etd > time_ref:
|
||||
raise ValidationError({"etd":f"'etd' is too far in the past. Incorrect datetime provided. Current Time: {time_ref}. ETD: {etd}."})
|
||||
|
||||
if eta is not None:
|
||||
raise ValidationError({"eta":f"'eta' should not be set when the shipcall type is 'departure'."})
|
||||
if etd > time_in_a_year:
|
||||
raise ValidationError({"etd":f"'etd' is more than a year in the future. ETD: {etd}."})
|
||||
|
||||
elif int(type_)==int(ShipcallType.shifting):
|
||||
if (eta is None) and (etd is None): # null values -> no violation
|
||||
@ -403,30 +409,45 @@ class InputValidationShipcall():
|
||||
# rules, a user is only allowed to provide *both* values.
|
||||
raise ValidationError({"eta_or_etd":f"For shifting shipcalls one should always provide, both, eta and etd."})
|
||||
|
||||
if (not eta > time_now) or (not etd > time_now):
|
||||
raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must be in the future. Incorrect datetime provided. Current Time: {time_now}. ETA: {eta}. ETD: {etd}"})
|
||||
if (not eta > time_ref) or (not etd > time_ref):
|
||||
raise ValidationError({"eta_or_etd":f"'eta' and 'etd' is too far in the past. Incorrect datetime provided. Current Time: {time_ref}. ETA: {eta}. ETD: {etd}"})
|
||||
if (not etd < eta):
|
||||
raise ValidationError({"eta_or_etd":f"The estimated time of departure ('etd') must take place *before the estimated time of arrival ('eta'). The ship cannot arrive, before it has departed. Found: ETD: {etd}, ETA: {eta}"})
|
||||
|
||||
if (eta is not None and etd is None) or (eta is None and etd is not None):
|
||||
raise ValidationError({"eta_or_etd":f"'eta' and 'etd' must both be provided when the shipcall type is 'shifting'."})
|
||||
if eta > time_in_a_year:
|
||||
raise ValidationError({"eta":f"'eta' is more than a year in the future. ETA: {eta}."})
|
||||
if etd > time_in_a_year:
|
||||
raise ValidationError({"etd":f"'etd' is more than a year in the future. ETD: {etd}."})
|
||||
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_tidal_window_in_future(type_, time_now, tidal_window_from, tidal_window_to):
|
||||
def check_tidal_window_in_future(type_, time_ref, tidal_window_from, tidal_window_to):
|
||||
|
||||
time_in_a_year = datetime.datetime.now().replace(datetime.datetime.now().year + 1)
|
||||
if tidal_window_to is not None:
|
||||
if not tidal_window_to >= time_now:
|
||||
raise ValidationError({"tidal_window_to":f"'tidal_window_to' must be in the future. Incorrect datetime provided."})
|
||||
if not tidal_window_to >= time_ref:
|
||||
raise ValidationError({"tidal_window_to":f"'tidal_window_to' is too far in the past. Incorrect datetime provided."})
|
||||
if tidal_window_to > time_in_a_year:
|
||||
raise ValidationError({"tidal_window_to":f"'tidal_window_to' is more than a year in the future. Found: {tidal_window_to}."})
|
||||
|
||||
if tidal_window_from is not None:
|
||||
if not tidal_window_from >= time_now:
|
||||
raise ValidationError({"tidal_window_from":f"'tidal_window_from' must be in the future. Incorrect datetime provided."})
|
||||
|
||||
if not tidal_window_from >= time_ref:
|
||||
raise ValidationError({"tidal_window_from":f"'tidal_window_from' is too far in the past. Incorrect datetime provided."})
|
||||
if tidal_window_from > time_in_a_year:
|
||||
raise ValidationError({"tidal_window_from":f"'tidal_window_from' is more than a year in the future. Found: {tidal_window_from}."})
|
||||
|
||||
if (tidal_window_to is not None) and (tidal_window_from is not None):
|
||||
if tidal_window_to < tidal_window_from:
|
||||
raise ValidationError({"tidal_window_to_or_tidal_window_from":f"'tidal_window_to' must take place after 'tidal_window_from'. Incorrect datetime provided. Found 'tidal_window_to': {tidal_window_to}, 'tidal_window_from': {tidal_window_to}."})
|
||||
|
||||
if (tidal_window_to is not None and tidal_window_from is None) or (tidal_window_to is None and tidal_window_from is not None):
|
||||
raise ValidationError({"tidal_window_to_or_tidal_window_from":f"'tidal_window_to' and 'tidal_window_from' must both be provided."})
|
||||
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_participant_list_not_empty_when_user_is_agency(loadedModel):
|
||||
"""
|
||||
@ -435,10 +456,10 @@ class InputValidationShipcall():
|
||||
participants = loadedModel.get("participants", [])
|
||||
is_agency_participant = [ParticipantType.AGENCY in ParticipantType(participant.get("type")) for participant in participants]
|
||||
|
||||
if not any(is_agency_participant):
|
||||
if not any(is_agency_participant):
|
||||
raise ValidationError({"participants":f"One of the assigned participants *must* be of type 'ParticipantType.AGENCY'. Found list of participants: {participants}"})
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_shipcall_is_canceled(loadedModel, content):
|
||||
# read the shipcall_id from the PUT data
|
||||
@ -454,17 +475,20 @@ class InputValidationShipcall():
|
||||
if shipcall.get("canceled", False):
|
||||
raise ValidationError({"canceled":f"The shipcall with id 'shipcall_id' is canceled. A canceled shipcall may not be changed."})
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_required_fields_of_put_request(content:dict):
|
||||
shipcall_id = content.get("id", None)
|
||||
if shipcall_id is None:
|
||||
raise ValidationError({"id":f"A PUT request requires an 'id' to refer to."})
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_shipcall_id_exists(loadedModel):
|
||||
"""simply checks, whether the defined shipcall ID exists in the database. Otherwise, a PUT-request must fail."""
|
||||
shipcall_id = loadedModel.get("id")
|
||||
if shipcall_id is None:
|
||||
raise ValidationError({"id":"a shipcall id must be provided"})
|
||||
|
||||
query = 'SELECT * FROM shipcall where (id = ?shipcall_id?)'
|
||||
shipcalls = execute_sql_query_standalone(query=query, model=Shipcall, param={"shipcall_id" : shipcall_id})
|
||||
if len(shipcalls)==0:
|
||||
@ -479,12 +503,12 @@ class InputValidationShipcall():
|
||||
a) belong to the ASSIGNED agency participant group
|
||||
b) belong to a BSMD participant, if the assigned agency has enabled the bit flag
|
||||
|
||||
When there is not yet an assigned agency for the respective shipcall, the request fails, and the user is considered as not authorized.
|
||||
This mechanism prevents self-assignment of an agency to arbitrary shipcalls.
|
||||
When there is not yet an assigned agency for the respective shipcall, only BSMD users are authorized.
|
||||
This mechanism prevents self-assignment of an agency to arbitrary shipcalls.
|
||||
"""
|
||||
### preparation ###
|
||||
# use the decoded JWT token and extract the participant type & participant id
|
||||
participant_id = user_data.get("participant_id")
|
||||
user_participant_id = user_data.get("participant_id")
|
||||
participant_type = get_participant_type_from_user_data(user_data)
|
||||
user_is_bsmd = (ParticipantType.BSMD in participant_type)
|
||||
|
||||
@ -494,44 +518,42 @@ class InputValidationShipcall():
|
||||
### AGENCY in SPM ###
|
||||
# determine, who is assigned as the agency for the shipcall
|
||||
if shipcall_participant_map is None:
|
||||
query = 'SELECT * FROM shipcall_participant_map where (shipcall_id = ?shipcall_id? AND type=?participant_type?)'
|
||||
assigned_agency = execute_sql_query_standalone(query=query, model=ShipcallParticipantMap, param={"shipcall_id" : shipcall_id, "participant_type":int(ParticipantType.AGENCY)})
|
||||
# query = 'SELECT * FROM shipcall_participant_map where (shipcall_id = ?shipcall_id? AND type=?participant_type?)'
|
||||
# assigned_agency = execute_sql_query_standalone(query=query, model=ShipcallParticipantMap, param={"shipcall_id" : shipcall_id, "participant_type":int(ParticipantType.AGENCY)})
|
||||
assigned_agency = get_assigned_participant_of_type(shipcall_id, participant_type=ParticipantType.AGENCY)
|
||||
an_agency_is_assigned = True if assigned_agency is not None else False
|
||||
|
||||
else:
|
||||
assigned_agency = [spm for spm in shipcall_participant_map if int(spm.type) == int(ParticipantType.AGENCY)]
|
||||
|
||||
an_agency_is_assigned = len(assigned_agency)==1
|
||||
if len(assigned_agency)>1:
|
||||
raise ValidationError({"internal_error":f"Internal error? Found more than one assigned agency for the shipcall with ID {shipcall_id}. Found: {assigned_agency}"})
|
||||
|
||||
if an_agency_is_assigned:
|
||||
# Agency assigned? User must belong to the assigned agency or be a BSMD user, in case the flag is set
|
||||
assigned_agency = [spm for spm in shipcall_participant_map if int(spm.type) == int(ParticipantType.AGENCY)]
|
||||
an_agency_is_assigned = len(assigned_agency)==1
|
||||
|
||||
if len(assigned_agency)>1:
|
||||
raise ValidationError({"internal_error":f"Internal error? Found more than one assigned agency for the shipcall with ID {shipcall_id}. Found: {assigned_agency}"})
|
||||
assigned_agency = assigned_agency[0]
|
||||
|
||||
# determine, whether the assigned agency has set the BSMD-flag to allow BSMD users to edit their assigned shipcalls
|
||||
query = 'SELECT * FROM participant where (id = ?participant_id?)'
|
||||
agency_participant = execute_sql_query_standalone(query=query, param={"participant_id" : participant_id}, command_type="single", model=Participant)
|
||||
if an_agency_is_assigned:
|
||||
assert isinstance(assigned_agency, Participant), f"expecting the assigency agency to be a Participant object. Found: {type(assigned_agency)}"
|
||||
assert isinstance(assigned_agency.flags, int), f"this method has currently only been developed with 'flags' being set as an integer. Found: {type(assigned_agency.flags)}"
|
||||
|
||||
assert isinstance(agency_participant.flags, int), f"this method has currently only been developed with 'flags' being set as an integer. Found: {type(agency_participant.flags)}"
|
||||
agency_has_bsmd_flag = agency_participant.flags==1 # once the flags are an IntFlag, change the boolean check to: (ParticipantFlag.BSMD in agency_participant.flags)
|
||||
# determine, whether the assigned agency has set the BSMD-flag to allow BSMD users to edit their assigned shipcalls
|
||||
agency_has_bsmd_flag = assigned_agency.flags==1 # once the flags are an IntFlag, change the boolean check to: (ParticipantFlag.BSMD in agency_participant.flags)
|
||||
|
||||
### USER authority ###
|
||||
# determine, whether the user is a) the assigned agency or b) a BSMD participant
|
||||
user_is_assigned_agency = (participant_id == assigned_agency.participant_id)
|
||||
user_is_assigned_agency = (user_participant_id == assigned_agency.id)
|
||||
|
||||
# when the BSMD flag is set: the user must be either BSMD or the assigned agency
|
||||
# when the BSMD flag is not set: the user must be the assigned agency
|
||||
user_is_authorized = (user_is_bsmd or user_is_assigned_agency) if agency_has_bsmd_flag else user_is_assigned_agency
|
||||
user_is_authorized = (user_is_bsmd or user_is_assigned_agency) #if agency_has_bsmd_flag else user_is_assigned_agency
|
||||
|
||||
if not user_is_authorized:
|
||||
raise werkzeug.exceptions.Forbidden(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). Assigned Agency: {assigned_agency} with Flags: {agency_participant.flags}") # Forbidden: 403
|
||||
|
||||
raise werkzeug.exceptions.Forbidden(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). Assigned Agency: {assigned_agency} with Flags: {assigned_agency.flags}") # Forbidden: 403
|
||||
|
||||
else:
|
||||
# when there is no assigned agency, only BSMD users can update the shipcall
|
||||
user_is_authorized = user_is_bsmd
|
||||
|
||||
if not user_is_authorized:
|
||||
if not user_is_bsmd:
|
||||
raise werkzeug.exceptions.Forbidden(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). There is no assigned agency yet, so only BSMD users can change datasets.") # part of a pytest.raises. Forbidden: 403
|
||||
|
||||
return
|
||||
|
||||
|
||||
@ -15,6 +15,8 @@ from BreCal.database.enums import ParticipantType, ParticipantFlag
|
||||
from BreCal.validators.input_validation_utils import check_if_user_is_bsmd_type, check_if_ship_id_is_valid, check_if_berth_id_is_valid, check_if_participant_ids_are_valid, check_if_participant_ids_and_types_are_valid, check_if_shipcall_id_is_valid, get_shipcall_id_dictionary, get_participant_type_from_user_data, get_participant_id_dictionary, check_if_participant_id_is_valid_standalone
|
||||
from BreCal.database.sql_queries import SQLQuery
|
||||
from BreCal.database.sql_handler import execute_sql_query_standalone
|
||||
from BreCal.database.sql_handler import get_assigned_participant_of_type
|
||||
from BreCal.database.sql_utils import get_times_data_for_id
|
||||
from BreCal.validators.validation_base_utils import check_if_int_is_valid_flag, check_if_string_has_special_characters
|
||||
import werkzeug
|
||||
|
||||
@ -23,7 +25,7 @@ def build_post_data_type_dependent_required_fields_dict()->dict[ShipcallType,dic
|
||||
The required fields of a POST-request depend on ShipcallType and ParticipantType. This function creates
|
||||
a dictionary, which maps those types to a list of required fields.
|
||||
|
||||
The participant types 'undefined' and 'bsmd' should not be used in POST-requests. They return 'None'.
|
||||
The participant types 'undefined' and 'bsmd' should not be used in POST-requests. They return 'None'.
|
||||
"""
|
||||
post_data_type_dependent_required_fields_dict = {
|
||||
ShipcallType.arrival:{
|
||||
@ -69,7 +71,7 @@ class InputValidationTimes():
|
||||
Example:
|
||||
InputValidationTimes.evaluate(user_data, loadedModel, content)
|
||||
|
||||
When the data violates one of the rules, a marshmallow.ValidationError is raised, which details the issues.
|
||||
When the data violates one of the rules, a marshmallow.ValidationError is raised, which details the issues.
|
||||
"""
|
||||
def __init__(self) -> None:
|
||||
pass
|
||||
@ -91,12 +93,16 @@ class InputValidationTimes():
|
||||
# 4.) Value checking
|
||||
InputValidationTimes.check_dataset_values(user_data, loadedModel, content)
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def evaluate_put_data(user_data:dict, loadedModel:dict, content:dict):
|
||||
# 1.) Check for the presence of required fields
|
||||
InputValidationTimes.check_times_required_fields_put_data(content)
|
||||
|
||||
# 1.5) Load model from database and set existing fields from dict to model
|
||||
old_times = get_times_data_for_id(content["id"])
|
||||
loadedModel = {**old_times, **loadedModel}
|
||||
|
||||
# 2.) Only users of the same participant_id, which the times dataset refers to, can update the entry
|
||||
InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=loadedModel, times_id=None)
|
||||
|
||||
@ -106,7 +112,7 @@ class InputValidationTimes():
|
||||
# 4.) Value checking
|
||||
InputValidationTimes.check_dataset_values(user_data, loadedModel, content)
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def evaluate_delete_data(user_data:dict, times_id:typing.Optional[int]):
|
||||
# 0.) an ID reference must be provided and will be converted to int
|
||||
@ -118,15 +124,16 @@ class InputValidationTimes():
|
||||
InputValidationTimes.check_if_entry_is_already_deleted(times_id)
|
||||
|
||||
# 2.) Only users of the same participant_id, which the times dataset refers to, can delete the entry
|
||||
InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=None, times_id=times_id)
|
||||
if not check_if_user_is_bsmd_type(user_data):
|
||||
InputValidationTimes.check_user_belongs_to_same_group_as_dataset_determines(user_data, loadedModel=None, times_id=times_id)
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_if_entry_is_already_deleted(times_id:int):
|
||||
"""
|
||||
When calling a delete request for times, the dataset may not be deleted already. This method
|
||||
makes sure, that the request contains and ID, has a matching entry in the database.
|
||||
When a times dataset is deleted, it is directly removed from the database.
|
||||
makes sure, that the request contains and ID, has a matching entry in the database.
|
||||
When a times dataset is deleted, it is directly removed from the database.
|
||||
|
||||
To identify deleted entries, query from the database and check, whether there is a match for the times id.
|
||||
|
||||
@ -146,19 +153,19 @@ class InputValidationTimes():
|
||||
if is_bsmd:
|
||||
raise ValidationError({"participant_type":f"current user belongs to BSMD. Cannot post 'times' datasets. Found user data: {user_data}"})
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_dataset_values(user_data:dict, loadedModel:dict, content:dict):
|
||||
"""
|
||||
this method validates POST and PUT data. Most of the dataset arguments are validated directly in the
|
||||
BreCal.schemas.model.TimesSchema, using @validates. This is exclusive for 'simple' validation rules.
|
||||
this method validates POST and PUT data. Most of the dataset arguments are validated directly in the
|
||||
BreCal.schemas.model.TimesSchema, using @validates. This is exclusive for 'simple' validation rules.
|
||||
|
||||
This applies to:
|
||||
"remarks" & "berth_info"
|
||||
"eta_berth", "etd_berth", "lock_time", "zone_entry", "operations_start", "operations_end"
|
||||
"""
|
||||
# while InputValidationTimes.check_user_is_not_bsmd_type already validates a user, this method
|
||||
# validates the times dataset.
|
||||
# validates the times dataset.
|
||||
|
||||
# ensure loadedModel["participant_type"] is of type ParticipantType
|
||||
if not isinstance(loadedModel["participant_type"], ParticipantType):
|
||||
@ -166,18 +173,18 @@ class InputValidationTimes():
|
||||
|
||||
if ParticipantType.BSMD in loadedModel["participant_type"]:
|
||||
raise ValidationError({"participant_type":f"current user belongs to BSMD. Cannot post times datasets. Found user data: {user_data}"})
|
||||
|
||||
if (loadedModel["etd_interval_end"] is not None) and (loadedModel["etd_berth"] is not None):
|
||||
|
||||
if ("etd_interval_end" in loadedModel and loadedModel["etd_interval_end"] is not None) and ("etd_berth" in loadedModel and loadedModel["etd_berth"] is not None):
|
||||
time_end_after_time_start = loadedModel["etd_interval_end"] >= loadedModel["etd_berth"]
|
||||
if not time_end_after_time_start:
|
||||
raise ValidationError({"etd":f"The provided time interval for the estimated departure time is invalid. The interval end takes place before the interval start. Found interval data: {loadedModel['etd_berth']} to {loadedModel['etd_interval_end']}"})
|
||||
|
||||
if (loadedModel["eta_interval_end"] is not None) and (loadedModel["eta_berth"] is not None):
|
||||
if ("eta_interval_end" in loadedModel and loadedModel["eta_interval_end"] is not None) and ("eta_berth" in loadedModel and loadedModel["eta_berth"] is not None):
|
||||
time_end_after_time_start = loadedModel["eta_interval_end"] >= loadedModel["eta_berth"]
|
||||
if not time_end_after_time_start:
|
||||
raise ValidationError({"eta":f"The provided time interval for the estimated arrival time is invalid. The interval begin takes place after the interval end. Found interval data: {loadedModel['eta_berth']} to {loadedModel['eta_interval_end']}"})
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_dataset_references(content:dict):
|
||||
"""
|
||||
@ -192,25 +199,25 @@ class InputValidationTimes():
|
||||
valid_berth_id_reference = check_if_berth_id_is_valid(berth_id)
|
||||
if not valid_berth_id_reference:
|
||||
raise ValidationError({"berth_id":f"The referenced berth_id '{berth_id}' does not exist in the database."})
|
||||
|
||||
|
||||
valid_shipcall_id_reference = check_if_shipcall_id_is_valid(shipcall_id)
|
||||
if not valid_shipcall_id_reference:
|
||||
raise ValidationError({"shipcall_id":f"The referenced shipcall_id '{shipcall_id}' does not exist in the database."})
|
||||
|
||||
|
||||
valid_participant_id_reference = check_if_participant_id_is_valid_standalone(participant_id, participant_type=None)
|
||||
if not valid_participant_id_reference:
|
||||
raise ValidationError({"participant_id":f"The referenced participant_id '{participant_id}' does not exist in the database."})
|
||||
|
||||
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_times_required_fields_post_data(loadedModel:dict, content:dict):
|
||||
"""
|
||||
Depending on ShipcallType and ParticipantType, there is a rather complex set of required fields.
|
||||
Independent of those types, any POST request for times should always include the default fields.
|
||||
Independent of those types, any POST request for times should always include the default fields.
|
||||
|
||||
The dependent and independent fields are validated by checking, whether the respective value in 'content'
|
||||
is undefined (returns None). When any of these fields is undefined, a ValidationError is raised.
|
||||
is undefined (returns None). When any of these fields is undefined, a ValidationError is raised.
|
||||
"""
|
||||
participant_type = loadedModel["participant_type"]
|
||||
shipcall_id = loadedModel["shipcall_id"]
|
||||
@ -240,14 +247,14 @@ class InputValidationTimes():
|
||||
verbosity_tuple = [(field, missing) for field, missing in zip(required_fields, missing_required_fields) if missing]
|
||||
raise ValidationError({"required_fields":f"At least one of the required fields is missing. Missing: {verbosity_tuple}"})
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_times_required_fields_put_data(content:dict):
|
||||
"""in a PUT request, only the 'id' is a required field. All other fields are simply ignored, when they are not provided."""
|
||||
if content.get("id") is None:
|
||||
raise ValidationError({"id":f"A PUT-request requires an 'id' reference, which was not found."})
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_post_data_type_independent_fields()->list[str]:
|
||||
"""
|
||||
@ -257,16 +264,16 @@ class InputValidationTimes():
|
||||
"shipcall_id", "participant_id", "participant_type"
|
||||
]
|
||||
return independent_required_fields
|
||||
|
||||
|
||||
@staticmethod
|
||||
def get_post_data_type_dependent_fields(shipcall_type:typing.Union[int, ShipcallType], participant_type:typing.Union[int, ParticipantType]):
|
||||
"""
|
||||
Depending on ShipcallType and ParticipantType, there is a rather complex set of required fields.
|
||||
|
||||
Arriving shipcalls need arrival times (e.g., 'eta'), Departing shipcalls need departure times (e.g., 'etd') and
|
||||
Shifting shipcalls need both times (e.g., 'eta' and 'etd').
|
||||
Arriving shipcalls need arrival times (e.g., 'eta'), Departing shipcalls need departure times (e.g., 'etd') and
|
||||
Shifting shipcalls need both times (e.g., 'eta' and 'etd').
|
||||
|
||||
Further, the ParticipantType determines the set of relevant times. In particular, the terminal uses
|
||||
Further, the ParticipantType determines the set of relevant times. In particular, the terminal uses
|
||||
'operations_start' and 'operations_end', while other users use 'eta_berth' or 'etd_berth'.
|
||||
"""
|
||||
# ensure that both, shipcall_type and participant_type, refer to the enumerators, as opposed to integers.
|
||||
@ -282,7 +289,7 @@ class InputValidationTimes():
|
||||
dependent_required_fields = dependent_required_fields_dict.get(shipcall_type,{}).get(participant_type,[])
|
||||
dependent_required_fields = dependent_required_fields if dependent_required_fields is not None else []
|
||||
return dependent_required_fields
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_if_user_fits_shipcall_participant_map(user_data:dict, loadedModel:dict, content:dict, spm_shipcall_data=None):
|
||||
"""
|
||||
@ -290,15 +297,15 @@ class InputValidationTimes():
|
||||
which is assigned to the shipcall within the ShipcallParticipantMap
|
||||
|
||||
This method does not validate, what the POST-request contains, but it validates, whether the *user* is
|
||||
authorized to send the request.
|
||||
authorized to send the request.
|
||||
|
||||
This method also checks for a special case: when an assigned AGENCY participant has the .BSMD flag enabled,
|
||||
a user of type BSMD may also post the times dataset.
|
||||
|
||||
options:
|
||||
spm_shipcall_data:
|
||||
spm_shipcall_data:
|
||||
data from the ShipcallParticipantMap, which refers to the respective shipcall ID. The SPM can be
|
||||
an optional argument to allow for much easier unit testing.
|
||||
an optional argument to allow for much easier unit testing.
|
||||
"""
|
||||
### TIMES DATASET (ShipcallParticipantMap) ###
|
||||
# identify shipcall_id
|
||||
@ -310,13 +317,13 @@ class InputValidationTimes():
|
||||
# read the ShipcallParticipantMap entry of the current shipcall_id. This is used within the input validation of a PUT request
|
||||
# creates a list of {'participant_id: ..., 'type': ...} elements
|
||||
spm_shipcall_data = execute_sql_query_standalone(
|
||||
query = "SELECT participant_id, type FROM shipcall_participant_map WHERE (shipcall_id=?shipcall_id? AND type=?type?)",
|
||||
query = "SELECT participant_id, type FROM shipcall_participant_map WHERE (shipcall_id=?shipcall_id? AND type=?type?)",
|
||||
param={"shipcall_id":shipcall_id, "type":int(DATASET_participant_type)},
|
||||
pooledConnection=None
|
||||
)
|
||||
|
||||
|
||||
DATASET_participant_id = InputValidationTimes.get_participant_id_from_shipcall_participant_map(shipcall_id, participant_type=DATASET_participant_type, spm_shipcall_data=spm_shipcall_data)
|
||||
|
||||
|
||||
### USER DATA (token) ###
|
||||
# identify user's participant_id & type (get all participants; then filter these for the {participant_id})
|
||||
user_participant_id = user_data["participant_id"] #participants = get_participant_id_dictionary() #participant_type = ParticipantType(participants.get(participant_id,{}).get("type"))
|
||||
@ -326,7 +333,7 @@ class InputValidationTimes():
|
||||
if (special_case__bsmd_may_edit_agency_dataset):
|
||||
# when a BSMD user posts a dataset of an AGENCY with BSMD-flag, there is no violation
|
||||
return
|
||||
|
||||
|
||||
# check, if participant_id is assigned to the ShipcallParticipantMap
|
||||
matching_spm = [
|
||||
spm
|
||||
@ -337,7 +344,7 @@ class InputValidationTimes():
|
||||
if not len(matching_spm)>0:
|
||||
raise ValidationError({"participant_id":f'The participant group with id {user_participant_id} is not assigned to the shipcall. Found ShipcallParticipantMap: {spm_shipcall_data}'}) # part of a pytest.raises
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_if_entry_already_exists_for_participant_type(user_data:dict, loadedModel:dict, content:dict):
|
||||
"""determines, whether a dataset for the participant type is already present"""
|
||||
@ -356,7 +363,7 @@ class InputValidationTimes():
|
||||
if participant_type_exists_already:
|
||||
raise ValidationError({"participant_type":f"A dataset for the participant type is already present. Participant Type: {participant_type}. Times Datasets: {times}"})
|
||||
return
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_user_belongs_to_same_group_as_dataset_determines(user_data:dict, loadedModel:typing.Optional[dict]=None, times_id:typing.Optional[int]=None, pdata:typing.Optional[list[dict]]=None):
|
||||
"""
|
||||
@ -371,7 +378,7 @@ class InputValidationTimes():
|
||||
times_id is used to directly identify the matching times entry
|
||||
|
||||
A special exception takes place, when a participant of type AGENCY is involved. In those times-entries, users with the
|
||||
IS_BSMD-Flag may also edit the entry.
|
||||
IS_BSMD-Flag may also edit the entry.
|
||||
"""
|
||||
assert not ((loadedModel is None) and (times_id is None)), f"must provide either loadedModel OR times_id. Both are 'None'"
|
||||
assert (loadedModel is None) or (times_id is None), f"must provide either loadedModel OR times_id. Both are defined."
|
||||
@ -379,62 +386,86 @@ class InputValidationTimes():
|
||||
# identify the user's participant id
|
||||
user_participant_id = user_data["participant_id"]
|
||||
|
||||
""" # #TODO:
|
||||
First of all, this method is shared for PUT and DELETE requests.
|
||||
PUT) is based on the loadedModel
|
||||
DELETE) is based on the times_id
|
||||
Both of them share the {user_data}-argument
|
||||
|
||||
These arguments are used to obtain shipcall_id, participant_type (of the times entry) and times_assigned_participant
|
||||
|
||||
there should be the following authorization approaches
|
||||
a) the user has the participant ID of the assigned entry for a given role
|
||||
for this, we need:
|
||||
1) user_participant_id
|
||||
2) times_participant_type
|
||||
3) SPM: assigned participant of the respective type (times_assigned_participant)
|
||||
_ = get_assigned_participant_of_type(shipcall_id, participant_type=ParticipantType.WHATTYPE)
|
||||
|
||||
b) the user is the assigned agency (or the BSMD if allowed)
|
||||
for this, we need:
|
||||
1) assigned_agency
|
||||
assigned_agency = get_assigned_participant_of_type(shipcall_id, participant_type=ParticipantType.AGENCY)
|
||||
2) agency's flag
|
||||
assigned_agency.flags
|
||||
3) user_is_bsmd boolean
|
||||
"""
|
||||
|
||||
# commonly used in the PUT-request
|
||||
if loadedModel is not None:
|
||||
shipcall_id = loadedModel["shipcall_id"]
|
||||
participant_type = loadedModel["participant_type"]
|
||||
|
||||
# get the matching entry from the shipcall participant map. Raise an error, when there is no match.
|
||||
participant_id_of_times_dataset = InputValidationTimes.get_participant_id_from_shipcall_participant_map(shipcall_id, participant_type)
|
||||
(shipcall_id, times_assigned_participant) = InputValidationTimes.prepare_authority_check_for_put_request(loadedModel)
|
||||
|
||||
# commonly used in the DELETE-request
|
||||
if times_id is not None:
|
||||
if pdata is None: # regular behavior. pdata is only defined in unit tests.
|
||||
# perform an SQL query. Creates a pooled connection internally, queries the database, then closes the connection.
|
||||
query = "SELECT participant_id, participant_type, shipcall_id FROM times WHERE id = ?id?"
|
||||
pdata = execute_sql_query_standalone(query=query, param={"id":times_id}, pooledConnection=None)
|
||||
# #TODO_refactor:
|
||||
(shipcall_id, times_assigned_participant) = InputValidationTimes.prepare_authority_check_for_delete_request(times_id, pdata)
|
||||
|
||||
# extracts the participant_id from the first matching entry, if applicable
|
||||
if not len(pdata)>0:
|
||||
# this case is usually covered by the InputValidationTimes.check_if_entry_is_already_deleted method already
|
||||
raise ValidationError({"times_id":f"Unknown times_id. Could not find a matching entry for ID: {times_id}"})
|
||||
else:
|
||||
participant_type = pdata[0].get("participant_type")
|
||||
shipcall_id = pdata[0].get("shipcall_id")
|
||||
# get the matching entry from the shipcall participant map, where the role matches. Raise an error, when there is no match.
|
||||
assigned_agency = get_assigned_participant_of_type(shipcall_id, participant_type=ParticipantType.AGENCY)
|
||||
|
||||
# get the matching entry from the shipcall participant map. Raise an error, when there is no match.
|
||||
participant_id_of_times_dataset = pdata[0].get("participant_id")
|
||||
# participant_id_of_times_dataset = InputValidationTimes.get_participant_id_from_shipcall_participant_map(shipcall_id, participant_type)
|
||||
# a) the user has the participant ID of the assigned entry for a given role
|
||||
user_is_assigned_role = user_participant_id == times_assigned_participant.id
|
||||
|
||||
# b) the user is the assigned agency
|
||||
user_is_assigned_agency = user_participant_id == assigned_agency.id
|
||||
|
||||
# c) the user is BSMD, if the assigned agency allows that
|
||||
assigned_agency_has_bsmd_flag = assigned_agency.flags == 1
|
||||
user_is_bsmd_type = check_if_user_is_bsmd_type(user_data={"participant_id":user_participant_id})
|
||||
user_is_bsmd_and_assigned_agency_has_flag = assigned_agency_has_bsmd_flag & user_is_bsmd_type
|
||||
|
||||
if user_is_assigned_role:
|
||||
return
|
||||
|
||||
elif user_is_assigned_agency:
|
||||
return
|
||||
|
||||
elif user_is_bsmd_and_assigned_agency_has_flag:
|
||||
return
|
||||
|
||||
else:
|
||||
times_participant_id = loadedModel.get("participant_id")
|
||||
raise ValidationError({"user_participant_type": f"The dataset may only be changed by a user belonging to the same participant group as the times dataset is referring to. Alternatively, the assigned agency may edit and delete the dataset. As a special case, BSMD users may edit and delete times datasets, when the assigned agency allows that. User participant_id: {user_participant_id}; Dataset participant_id: {times_participant_id}"})
|
||||
|
||||
# when the user's participant id is different from the times dataset, an exception is raised
|
||||
if user_participant_id != participant_id_of_times_dataset:
|
||||
# for some AGENCY participants, users with the BSMD flag may also edit the datasets
|
||||
special_case__bsmd_may_edit_agency_dataset = InputValidationTimes.check_if_bsmd_may_edit_agency_dataset(user_participant_id, participant_id_of_times_dataset, participant_type)
|
||||
if special_case__bsmd_may_edit_agency_dataset:
|
||||
return
|
||||
else:
|
||||
raise ValidationError({"user_participant_type":f"The dataset may only be changed by a user belonging to the same participant group as the times dataset is referring to. User participant_id: {user_participant_id}; Dataset participant_id: {participant_id_of_times_dataset}"})
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def get_participant_id_from_shipcall_participant_map(shipcall_id:int, participant_type:int, spm_shipcall_data=None)->int:
|
||||
def get_participant_id_from_shipcall_participant_map(shipcall_id:typing.Optional[int], participant_type:int, spm_shipcall_data=None)->int:
|
||||
"""use shipcall_id and participant_type to identify the matching participant_id"""
|
||||
if shipcall_id is None:
|
||||
raise ValidationError({"shipcall_id":f"Could not find a referenced shipcall_id within the request."})
|
||||
|
||||
if spm_shipcall_data is None:
|
||||
spm_shipcall_data = execute_sql_query_standalone(
|
||||
query=SQLQuery.get_shipcall_participant_map_by_shipcall_id_and_type(),
|
||||
param={"id":shipcall_id, "type":participant_type},
|
||||
query=SQLQuery.get_shipcall_participant_map_by_shipcall_id_and_type(),
|
||||
param={"id":shipcall_id, "type":participant_type},
|
||||
command_type="query") # returns a list of matches
|
||||
|
||||
|
||||
# raise an error when there are no matches
|
||||
if len(spm_shipcall_data)==0:
|
||||
raise ValidationError({"participant_type":f"Could not find a matching time dataset for the provided participant_type: {participant_type} at shipcall with id {shipcall_id}."})
|
||||
|
||||
|
||||
participant_id_of_times_dataset = spm_shipcall_data[0].get("participant_id")
|
||||
return participant_id_of_times_dataset
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_if_bsmd_may_edit_agency_dataset(user_participant_id:int, participant_id_of_times_dataset:int, participant_type:ParticipantType)->bool:
|
||||
"""
|
||||
@ -448,45 +479,81 @@ class InputValidationTimes():
|
||||
|
||||
args:
|
||||
user_participant_id: ID of the user, obtained from the jwt-token
|
||||
participant_id_of_times_dataset: assigned participant of the shipcall, obtained from the ShipcallParticipantMap
|
||||
participant_id_of_times_dataset: assigned participant of the shipcall, obtained from the ShipcallParticipantMap
|
||||
"""
|
||||
# when the participant type of the dataset is not an AGENCY, this exception rule does not take place
|
||||
# when the participant type of the dataset is not an AGENCY, this exception rule does not take place
|
||||
dataset_participant_type_is_agency = int(participant_type)==int(ParticipantType.AGENCY)
|
||||
if not dataset_participant_type_is_agency:
|
||||
return False
|
||||
|
||||
|
||||
### TIMES ENTRY (ShipcallParticipantMap) ###
|
||||
# identify, whether the dataset's assigned participant has the BSMD flag
|
||||
agency_has_bsmd_flag = InputValidationTimes.check_if_participant_has_bsmd_flag(participant_id=participant_id_of_times_dataset)
|
||||
|
||||
|
||||
### USER DATA (token) ###
|
||||
# determine, whether the user is of participant_type BSMD
|
||||
user_is_bsmd_type = check_if_user_is_bsmd_type(user_data={"participant_id":user_participant_id})
|
||||
return (agency_has_bsmd_flag) & (user_is_bsmd_type)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def check_if_participant_has_bsmd_flag(participant_id:int)->bool:
|
||||
"""
|
||||
Given a participant_id, this method checks, whether the participant with {participant_id}
|
||||
has the .BSMD flag in the .flags field.
|
||||
has the .BSMD flag in the .flags field.
|
||||
"""
|
||||
# get the dataset's assigned Participant, which matches the SPM entry
|
||||
participant = execute_sql_query_standalone(
|
||||
SQLQuery.get_participant_from_id(),
|
||||
param={"participant_id":participant_id},
|
||||
command_type="single",
|
||||
param={"participant_id":participant_id},
|
||||
command_type="single",
|
||||
pooledConnection=None)
|
||||
|
||||
|
||||
has_bsmd_flag = ParticipantFlag.BSMD in [ParticipantFlag(participant.get("flags"))]
|
||||
return has_bsmd_flag
|
||||
|
||||
@staticmethod
|
||||
def prepare_authority_check_for_put_request(loadedModel)->typing.Tuple[int,Participant]:
|
||||
"""extracts the loadedModel to obtain relevant arguments"""
|
||||
shipcall_id = loadedModel["shipcall_id"]
|
||||
participant_type = loadedModel["participant_type"]
|
||||
|
||||
# get the matching entry from the shipcall participant map, where the role matches. Raise an error, when there is no match.
|
||||
times_assigned_participant = get_assigned_participant_of_type(shipcall_id, participant_type=participant_type)
|
||||
|
||||
if times_assigned_participant is None:
|
||||
raise ValidationError({"participant_type":"the requested participant type is not assigned to the shipcall."})
|
||||
return (shipcall_id, times_assigned_participant)
|
||||
|
||||
@staticmethod
|
||||
def prepare_authority_check_for_delete_request(times_id, pdata=None)->typing.Tuple[int,Participant]:
|
||||
if pdata is None: # regular behavior. pdata is only defined in unit tests.
|
||||
# perform an SQL query. Creates a pooled connection internally, queries the database, then closes the connection.
|
||||
query = "SELECT participant_id, participant_type, shipcall_id FROM times WHERE id = ?id?"
|
||||
pdata = execute_sql_query_standalone(query=query, param={"id":times_id}, pooledConnection=None)
|
||||
|
||||
# extracts the participant_id from the first matching entry, if applicable
|
||||
if not len(pdata)>0:
|
||||
# this case is usually covered by the InputValidationTimes.check_if_entry_is_already_deleted method already
|
||||
raise ValidationError({"times_id":f"Unknown times_id. Could not find a matching entry for ID: {times_id}"})
|
||||
else:
|
||||
participant_type = pdata[0].get("participant_type")
|
||||
shipcall_id = pdata[0].get("shipcall_id")
|
||||
|
||||
# get the matching entry from the shipcall participant map, where the role matches. Raise an error, when there is no match.
|
||||
times_assigned_participant = get_assigned_participant_of_type(shipcall_id, participant_type=participant_type)
|
||||
|
||||
if times_assigned_participant is None:
|
||||
raise ValidationError({"participant_type":"the requested participant type is not assigned to the shipcall."})
|
||||
return (shipcall_id, times_assigned_participant)
|
||||
|
||||
|
||||
|
||||
def deprecated_build_post_data_type_dependent_required_fields_dict()->dict[ShipcallType,dict[ParticipantType,typing.Optional[list[str]]]]:
|
||||
"""
|
||||
The required fields of a POST-request depend on ShipcallType and ParticipantType. This function creates
|
||||
a dictionary, which maps those types to a list of required fields.
|
||||
|
||||
The participant types 'undefined' and 'bsmd' should not be used in POST-requests. They return 'None'.
|
||||
The participant types 'undefined' and 'bsmd' should not be used in POST-requests. They return 'None'.
|
||||
"""
|
||||
post_data_type_dependent_required_fields_dict = {
|
||||
ShipcallType.arrival:{
|
||||
|
||||
@ -41,7 +41,7 @@ def unbundle_validation_error_message(message):
|
||||
unbundle_(message, unbundled=unbundled)
|
||||
if len(unbundled)>0:
|
||||
error_field = "ValidationError in the following field(s): " + " & ".join([unb["error_field"] for unb in unbundled])
|
||||
error_description = "Error Description(s): " + " & ".join([unb["error_description"] for unb in unbundled])
|
||||
error_description = " " . join([unb["error_description"] for unb in unbundled])
|
||||
else:
|
||||
error_field = "ValidationError"
|
||||
error_description = "unknown validation error"
|
||||
|
||||
@ -3,7 +3,7 @@ import sys
|
||||
import logging
|
||||
|
||||
sys.path.insert(0, '/var/www/brecal_test/src/server')
|
||||
sys.path.insert(0, '/var/www/venv/lib/python3.10/site-packages/')
|
||||
sys.path.insert(0, '/var/www/venv/lib/python3.12/site-packages/')
|
||||
|
||||
import schedule
|
||||
|
||||
|
||||
@ -207,7 +207,7 @@ def test_shipcall_post_request_fails_when_voyage_string_is_invalid(get_stub_toke
|
||||
with pytest.raises(ValidationError, match="Longer than maximum length 16"):
|
||||
assert response.status_code==400
|
||||
raise ValidationError(response.json())
|
||||
|
||||
|
||||
# Fail: special characters
|
||||
post_data = original_post_data.copy()
|
||||
post_data["voyage"] = '👽'
|
||||
@ -226,17 +226,17 @@ def test_shipcall_post_request_fails_when_type_arrival_and_not_in_future(get_stu
|
||||
# accept
|
||||
post_data = original_post_data.copy()
|
||||
post_data["type"] = ShipcallType.arrival.name
|
||||
post_data["eta"] = (datetime.datetime.now() + datetime.timedelta(hours=3)).isoformat()
|
||||
post_data["eta"] = (datetime.datetime.now() + datetime.timedelta(days=2)).isoformat()
|
||||
response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data)
|
||||
assert response.status_code == 201
|
||||
|
||||
# error
|
||||
post_data = original_post_data.copy()
|
||||
post_data["type"] = ShipcallType.arrival.name
|
||||
post_data["eta"] = (datetime.datetime.now() - datetime.timedelta(hours=3)).isoformat()
|
||||
post_data["eta"] = (datetime.datetime.now() - datetime.timedelta(days=2)).isoformat()
|
||||
response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data)
|
||||
|
||||
with pytest.raises(ValidationError, match="must be in the future. Incorrect datetime provided"):
|
||||
with pytest.raises(ValidationError, match="is too far in the past. Incorrect datetime provided"):
|
||||
assert response.status_code==400
|
||||
raise ValidationError(response.json())
|
||||
return
|
||||
@ -256,10 +256,10 @@ def test_shipcall_post_request_fails_when_type_departure_and_not_in_future(get_s
|
||||
# error
|
||||
post_data = original_post_data.copy()
|
||||
post_data["type"] = ShipcallType.departure.name
|
||||
post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(hours=3)).isoformat()
|
||||
post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(days=3)).isoformat()
|
||||
response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data)
|
||||
|
||||
with pytest.raises(ValidationError, match="must be in the future. Incorrect datetime provided"):
|
||||
with pytest.raises(ValidationError, match="is too far in the past. Incorrect datetime provided"):
|
||||
assert response.status_code==400
|
||||
raise ValidationError(response.json())
|
||||
return
|
||||
@ -280,11 +280,11 @@ def test_shipcall_post_request_fails_when_type_shifting_and_not_in_future(get_st
|
||||
# error
|
||||
post_data = original_post_data.copy()
|
||||
post_data["type"] = ShipcallType.shifting.name
|
||||
post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(hours=3)).isoformat()
|
||||
post_data["etd"] = (datetime.datetime.now() - datetime.timedelta(days=3)).isoformat()
|
||||
post_data["eta"] = (datetime.datetime.now() + datetime.timedelta(hours=3,minutes=1)).isoformat()
|
||||
response = requests.post(f"{url}/shipcalls", headers={"Content-Type":"text", "Authorization":f"Bearer {token}"}, json=post_data)
|
||||
|
||||
with pytest.raises(ValidationError, match="must be in the future. Incorrect datetime provided"):
|
||||
with pytest.raises(ValidationError, match="is too far in the past. Incorrect datetime provided"):
|
||||
assert response.status_code==400
|
||||
raise ValidationError(response.json())
|
||||
return
|
||||
@ -629,7 +629,7 @@ def test_shipcall_put_request_fails_when_different_participant_id_is_assigned(ge
|
||||
{"id":99115, 'participant_id': 5, 'type': 8}]
|
||||
spm_shipcall_data = [
|
||||
{**{"created":created, "modified":modified, "shipcall_id":shipcall_id}, **spm}
|
||||
for spm in
|
||||
for spm in
|
||||
spm_shipcall_data
|
||||
]
|
||||
spm_shipcall_data = [ShipcallParticipantMap(**spm) for spm in spm_shipcall_data]
|
||||
@ -652,7 +652,7 @@ def test_shipcall_put_request_success(get_shipcall_id_after_stub_post_request):
|
||||
user_data = {'id':6, 'participant_id':1}
|
||||
loadedModel = post_data
|
||||
content = post_data
|
||||
|
||||
|
||||
created = datetime.datetime.now()+datetime.timedelta(minutes=1)
|
||||
modified = datetime.datetime.now()+datetime.timedelta(minutes=2)
|
||||
|
||||
@ -662,7 +662,7 @@ def test_shipcall_put_request_success(get_shipcall_id_after_stub_post_request):
|
||||
{"id":99115, 'participant_id': 5, 'type': 8}]
|
||||
spm_shipcall_data = [
|
||||
{**{"created":created, "modified":modified, "shipcall_id":shipcall_id}, **spm}
|
||||
for spm in
|
||||
for spm in
|
||||
spm_shipcall_data
|
||||
]
|
||||
spm_shipcall_data = [ShipcallParticipantMap(**spm) for spm in spm_shipcall_data]
|
||||
@ -687,7 +687,7 @@ def test_shipcall_put_request_fails_when_no_agency_is_assigned(get_shipcall_id_a
|
||||
user_data = {'id':6, 'participant_id':2} # participant_id 2 is not BSMD and is not authorized.
|
||||
loadedModel = post_data
|
||||
content = post_data
|
||||
|
||||
|
||||
created = datetime.datetime.now()+datetime.timedelta(minutes=1)
|
||||
modified = datetime.datetime.now()+datetime.timedelta(minutes=2)
|
||||
|
||||
@ -697,7 +697,7 @@ def test_shipcall_put_request_fails_when_no_agency_is_assigned(get_shipcall_id_a
|
||||
{"id":99115, 'participant_id': 5, 'type': 4}]
|
||||
spm_shipcall_data = [
|
||||
{**{"created":created, "modified":modified, "shipcall_id":shipcall_id}, **spm}
|
||||
for spm in
|
||||
for spm in
|
||||
spm_shipcall_data
|
||||
]
|
||||
spm_shipcall_data = [ShipcallParticipantMap(**spm) for spm in spm_shipcall_data]
|
||||
@ -705,7 +705,7 @@ def test_shipcall_put_request_fails_when_no_agency_is_assigned(get_shipcall_id_a
|
||||
|
||||
# no agency assigned
|
||||
ivs = InputValidationShipcall()
|
||||
|
||||
|
||||
with pytest.raises(werkzeug.exceptions.Forbidden, match=re.escape(f"PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). There is no assigned agency yet, so only BSMD users can change datasets.")):
|
||||
ivs.check_user_is_authorized_for_put_request(user_data, loadedModel, content, spm_shipcall_data)
|
||||
return
|
||||
@ -730,7 +730,7 @@ def test_shipcall_put_request_fails_when_user_is_not_authorized(get_shipcall_id_
|
||||
{"id":99115, 'participant_id': 5, 'type': 4}]
|
||||
spm_shipcall_data = [
|
||||
{**{"created":created, "modified":modified, "shipcall_id":shipcall_id}, **spm}
|
||||
for spm in
|
||||
for spm in
|
||||
spm_shipcall_data
|
||||
]
|
||||
spm_shipcall_data = [ShipcallParticipantMap(**spm) for spm in spm_shipcall_data]
|
||||
@ -754,20 +754,20 @@ def test_shipcall_put_request_fails_when_user_tries_self_assignment(get_shipcall
|
||||
|
||||
created = datetime.datetime.now()+datetime.timedelta(minutes=1)
|
||||
modified = datetime.datetime.now()+datetime.timedelta(minutes=2)
|
||||
|
||||
|
||||
spm_shipcall_data = [
|
||||
{"id":99113, 'participant_id': 3, 'type': 1},
|
||||
{"id":99114, 'participant_id': 4, 'type': 2},
|
||||
{"id":99115, 'participant_id': 5, 'type': 4}]
|
||||
spm_shipcall_data = [
|
||||
{**{"created":created, "modified":modified, "shipcall_id":shipcall_id}, **spm}
|
||||
for spm in
|
||||
for spm in
|
||||
spm_shipcall_data
|
||||
]
|
||||
spm_shipcall_data = [ShipcallParticipantMap(**spm) for spm in spm_shipcall_data]
|
||||
|
||||
|
||||
# self-assignment. User is participant 6, and wants to assign participant 6.
|
||||
# self-assignment. User is participant 6, and wants to assign participant 6.
|
||||
ivs = InputValidationShipcall()
|
||||
with pytest.raises(werkzeug.exceptions.Forbidden, match=re.escape("PUT Requests for shipcalls can only be issued by an assigned AGENCY or BSMD users (if the special-flag is enabled). There is no assigned agency yet, so only BSMD users can change datasets.")):
|
||||
# previous error message: An agency cannot self-register for a shipcall. The request is issued by an agency-user and tries to assign an AGENCY as the participant of the shipcall.""
|
||||
@ -813,7 +813,7 @@ def test_shipcall_put_request_works_if_most_values_are_null():
|
||||
|
||||
InputValidationShipcall.evaluate_put_data(user_data, loadedModel, content)
|
||||
return
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@ -860,10 +860,10 @@ def test_shipcall_put_request_fails_input_validation_shipcall_when_shipcall_is_c
|
||||
return
|
||||
|
||||
def test_post_data_with_valid_data(get_stub_token):
|
||||
"""This unit test uses the input data from
|
||||
"""This unit test uses the input data from
|
||||
# https://trello.com/c/VXVSLTF4/267-shipcall-anlegen-shifting-erh%C3%A4lt-fehler-aufgrund-fr%C3%BCherem-etd-als-eta
|
||||
|
||||
to make sure, the failure case no longer appears.
|
||||
to make sure, the failure case no longer appears.
|
||||
"""
|
||||
url, token = get_stub_token["url"], get_stub_token["token"]
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user