Skip to content

Commit

Permalink
Customizable gender field (#4027)
Browse files Browse the repository at this point in the history
  • Loading branch information
reiterl authored Sep 27, 2024
1 parent 7bdf456 commit f097ed2
Show file tree
Hide file tree
Showing 44 changed files with 668 additions and 80 deletions.
2 changes: 1 addition & 1 deletion client/src/app/domain/fieldsets/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { BaseSimplifiedModelRequest } from 'src/app/site/services/model-request-

export class UserFieldsets {
public static readonly FullNameSubscription: BaseSimplifiedModelRequest = {
fieldset: [`title`, `first_name`, `last_name`, `pronoun`, `username`, `gender`, `default_vote_weight`]
fieldset: [`title`, `first_name`, `last_name`, `pronoun`, `username`, `default_vote_weight`, `gender_id`]
};
}

Expand Down
21 changes: 21 additions & 0 deletions client/src/app/domain/models/gender/gender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Id } from '../../definitions/key-types';
import { BaseModel } from '../base/base-model';
/**
* Representation of a gender.
* @ignore
*/

export class Gender extends BaseModel<Gender> {
public static COLLECTION = `gender`;

public readonly name!: string;
public organization_id!: Id; // (organization/gender_ids)[]
public user_ids!: Id[]; // user/gender_id

public constructor(input?: Partial<Gender>) {
super(Gender.COLLECTION, input);
}

public static readonly REQUESTABLE_FIELDS: (keyof Gender)[] = [`id`, `name`, `organization_id`, `user_ids`];
}
export interface Gender {}
4 changes: 2 additions & 2 deletions client/src/app/domain/models/organizations/organization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ export class OrganizationSetting {
public saml_metadata_idp!: string;
public saml_metadata_sp!: string;
public saml_private_key!: string;
public genders!: string[];
}

export class Organization extends BaseModel<Organization> {
Expand All @@ -52,6 +51,7 @@ export class Organization extends BaseModel<Organization> {
public archived_meeting_ids!: Id[]; // (meeting/is_archived_in_organization_id)[];
public template_meeting_ids!: Id[]; // (meeting/template_for_organization_id)[];
public mediafile_ids!: Id[];
public gender_ids!: Id[]; // (gender/organization_id);
public published_mediafile_ids!: Id[];

public constructor(input?: any) {
Expand All @@ -66,7 +66,7 @@ export class Organization extends BaseModel<Organization> {
`privacy_policy`,
`login_text`,
`reset_password_verbose_errors`,
`genders`,
`gender_ids`,
`enable_electronic_voting`,
`enable_chat`,
`limit_of_meetings`,
Expand Down
2 changes: 1 addition & 1 deletion client/src/app/domain/models/users/user.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { marker as _ } from '@colsen1991/ngx-translate-extract-marker';

import { User } from './user';

export const userHeadersAndVerboseNames: { [key in keyof User]?: any } = {
export const userHeadersAndVerboseNames: { [key in keyof User | 'gender']?: any } = {
title: _(`Title`),
first_name: _(`Given name`),
last_name: _(`Surname`),
Expand Down
4 changes: 2 additions & 2 deletions client/src/app/domain/models/users/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export class User extends BaseDecimalModel<User> {
public readonly is_physical_person!: boolean;
public readonly default_password!: string;
public readonly can_change_own_password!: boolean;
public readonly gender!: string;
public readonly email!: string;
public readonly last_email_sent!: number; // comes in seconds
public readonly last_login!: number; // comes in seconds
Expand All @@ -52,6 +51,7 @@ export class User extends BaseDecimalModel<User> {

public organization_management_level!: keyof OMLMapping;
public committee_management_ids!: Id[];
public gender_id: Id; // (gender/user_ids)[]

public constructor(input?: Partial<User>) {
super(User.COLLECTION, input);
Expand All @@ -74,12 +74,12 @@ export class User extends BaseDecimalModel<User> {
`is_physical_person`,
`default_password`,
`can_change_own_password`,
`gender`,
`email`,
`default_vote_weight`,
`last_email_sent`,
`is_demo_user`,
`last_login`,
`gender_id`,
`organization_management_level`,
`is_present_in_meeting_ids`,
`committee_ids`,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';

import { GenderRepositoryService } from './gender-repository.service';

xdescribe(`GenderRepositoryService`, () => {
let service: GenderRepositoryService;

beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(GenderRepositoryService);
});

it(`should be created`, () => {
expect(service).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { Injectable } from '@angular/core';
import { Id } from 'src/app/domain/definitions/key-types';
import { Identifiable } from 'src/app/domain/interfaces';
import { Gender } from 'src/app/domain/models/gender/gender';
import { BaseRepository } from 'src/app/gateways/repositories/base-repository';
import { ViewGender } from 'src/app/site/pages/organization/pages/accounts/pages/gender/view-models/view-gender';
import { Fieldsets } from 'src/app/site/services/model-request-builder';

import { Action } from '../../actions';
import { RepositoryServiceCollectorService } from '../repository-service-collector.service';
import { GenderAction } from './gender.action';

@Injectable({
providedIn: `root`
})
export class GenderRepositoryService extends BaseRepository<ViewGender, Gender> {
public constructor(repositoryServiceCollector: RepositoryServiceCollectorService) {
super(repositoryServiceCollector, Gender);
}

public getVerboseName = (plural?: boolean): string => (plural ? `Genders` : `Gender`);
public getTitle = (viewModel: ViewGender): string => viewModel.name;
public override getFieldsets(): Fieldsets<any> {
const baseFields: (keyof Gender)[] = [];
const requiredFields: (keyof Gender)[] = baseFields.concat([`name`]);
return {
...super.getFieldsets(),
required: requiredFields
};
}

public create(...genders: any[]): Action<Identifiable[]> {
const payload = genders;
return this.createAction(GenderAction.CREATE, payload);
}

public update(update: any, id: Id): Action<void> {
const payload = {
id,
...update
};
return this.createAction(GenderAction.UPDATE, payload);
}

public delete(...ids: Id[]): Action<void> {
const payload = ids.map(id => ({ id }));
return this.createAction(GenderAction.DELETE, payload);
}
}
5 changes: 5 additions & 0 deletions client/src/app/gateways/repositories/gender/gender.action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export class GenderAction {
public static readonly CREATE = `gender.create`;
public static readonly UPDATE = `gender.update`;
public static readonly DELETE = `gender.delete`;
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,7 @@ export class OrganizationRepositoryService extends BaseRepository<ViewOrganizati
`default_language`,
`saml_metadata_idp`,
`saml_metadata_sp`,
`saml_private_key`,
`genders`
`saml_private_key`
);
return {
...super.getFieldsets(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
`last_name`,
`pronoun`,
`username` /* Required! To getShortName */,
`gender`,
`gender_id`,
`default_vote_weight`,
`is_physical_person`,
`is_active`,
Expand Down Expand Up @@ -226,7 +226,7 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
email: update.email,
username: update.username,
pronoun: update.pronoun,
gender: update.gender
gender_id: update.gender_id
};
return this.sendActionToBackend(UserAction.UPDATE_SELF, payload);
}
Expand Down Expand Up @@ -259,7 +259,7 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User> {
is_active: partialUser.is_active,
is_physical_person: partialUser.is_physical_person,
default_password: partialUser.default_password,
gender: partialUser.gender,
gender_id: partialUser.gender_id,
email: partialUser.email,
default_vote_weight: toDecimal(partialUser.default_vote_weight, false) as any,
organization_management_level: partialUser.organization_management_level,
Expand Down
14 changes: 14 additions & 0 deletions client/src/app/infrastructure/definitions/relations/relations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
import { ViewPollCandidate } from 'src/app/site/pages/meetings/pages/polls/view-models/view-poll-candidate';
import { ViewPollCandidateList } from 'src/app/site/pages/meetings/pages/polls/view-models/view-poll-candidate-list';
import { ViewMeetingUser } from 'src/app/site/pages/meetings/view-models/view-meeting-user';
import { ViewGender } from 'src/app/site/pages/organization/pages/accounts/pages/gender/view-models/view-gender';
import { ViewResource } from 'src/app/site/pages/organization/pages/resources';

import { BaseViewModel, ViewModelConstructor } from '../../../site/base/base-view-model';
Expand Down Expand Up @@ -165,6 +166,13 @@ export const RELATIONS: Relation[] = [
AField: `theme`,
BField: `theme_for_organization`
}),
...makeM2O({
OViewModel: ViewOrganization,
MViewModel: ViewGender,
OField: `genders`,
MField: `organization`,
isFullList: true
}),
// ########## Organization tags
...makeGenericM2M<ViewOrganizationTag, HasOrganizationTags>({
viewModel: ViewOrganizationTag,
Expand Down Expand Up @@ -272,6 +280,12 @@ export const RELATIONS: Relation[] = [
AField: `meeting_users`,
BField: `structure_levels`
}),
...makeM2O({
MViewModel: ViewUser,
OViewModel: ViewGender,
MField: `gender`,
OField: `users`
}),
// Vote delegations
// vote_delegated_to_id -> vote_delegations_from_ids
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { MotionsAppConfig } from 'src/app/site/pages/meetings/pages/motions';
import { PollsAppConfig } from 'src/app/site/pages/meetings/pages/polls/polls.config';
import { ProjectorAppConfig } from 'src/app/site/pages/meetings/pages/projectors/projector.config';
import { MainMenuService } from 'src/app/site/pages/meetings/services/main-menu.service';
import { GendersAppConfig } from 'src/app/site/pages/organization/pages/accounts/pages/gender/genders.config';
import { CollectionMapperService } from 'src/app/site/services/collection-mapper.service';
import { FallbackRoutesService } from 'src/app/site/services/fallback-routes.service';
import { ModelRequestBuilderService } from 'src/app/site/services/model-request-builder';
Expand Down Expand Up @@ -55,7 +56,8 @@ const appConfigs: AppConfig[] = [
MeetingSettingsAppConfig,
ChatAppConfig,
ActionWorkerAppConfig,
MeetingUserAppConfig
MeetingUserAppConfig,
GendersAppConfig
];

@Injectable({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,10 +193,14 @@ export class AccountDialogComponent extends BaseUiComponent implements OnInit {

public async saveUserChanges(): Promise<void> {
if (this.self) {
const payload = this.userPersonalForm;
if (payload.gender_id === 0) {
payload.gender_id = null;
}
if (this.operator.hasPerms(Permission.userCanUpdate) && this._isUserInScope) {
await this.repo.update(this.userPersonalForm, this.self).resolve();
await this.repo.update(payload, this.self).resolve();
} else {
await this.repo.updateSelf(this.userPersonalForm, this.self);
await this.repo.updateSelf(payload, this.self);
}
}
this.isUserFormValid = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,12 @@ <h2>{{ 'Personal information' | translate }}</h2>
<!-- Gender -->
<mat-form-field class="form58 force-min-width">
<mat-label>{{ 'Gender' | translate }}</mat-label>
<mat-select formControlName="gender">
<mat-option [value]="null">-</mat-option>
@for (gender of genders; track gender) {
<mat-option [value]="gender">
{{ gender | translate }}
</mat-option>
}
</mat-select>
<os-repo-search-selector
formControlName="gender_id"
[includeNone]="true"
[repo]="genderRepo"
[subscriptionConfig]="genderListSubscriptionConfig"
></os-repo-search-selector>
</mat-form-field>
<!-- Pronoun -->
<mat-form-field class="form38 force-min-width">
Expand Down Expand Up @@ -230,7 +228,7 @@ <h4>{{ 'Name' | translate }}</h4>
@if (user.gender) {
<div>
<h4>{{ 'Gender' | translate }}</h4>
<span>{{ user.gender | translate }}</span>
<span>{{ user.gender?.name | translate }}</span>
</div>
}
<!-- Mail -->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ import {
} from '@angular/forms';
import { Subscription } from 'rxjs';
import { createEmailValidator } from 'src/app/infrastructure/utils/validators/email';
import { getGenderListSubscriptionConfig } from 'src/app/site/pages/organization/pages/accounts/pages/gender/gender.subscription';
import { GenderControllerService } from 'src/app/site/pages/organization/pages/accounts/pages/gender/services/gender-controller.service';
import { OperatorService } from 'src/app/site/services/operator.service';
import { BaseUiComponent } from 'src/app/ui/base/base-ui-component';

import { GENDERS } from '../../../../../domain/models/users/user';
import { ViewUser } from '../../../../../site/pages/meetings/view-models/view-user';
import { OneOfValidator } from '../../validators';

Expand Down Expand Up @@ -123,12 +124,12 @@ export class UserDetailViewComponent extends BaseUiComponent implements OnInit,

public personalInfoForm!: UntypedFormGroup;

public genders = GENDERS;

public get isSelf(): boolean {
return this.operator.operatorId === this._user?.id;
}

public genderListSubscriptionConfig = getGenderListSubscriptionConfig();

private set _initialState(state: any | null) {
this._initialStateString = JSON.stringify(state);
}
Expand All @@ -153,6 +154,7 @@ export class UserDetailViewComponent extends BaseUiComponent implements OnInit,
public constructor(
private fb: UntypedFormBuilder,
private operator: OperatorService,
public genderRepo: GenderControllerService,
private cd: ChangeDetectorRef
) {
super();
Expand Down Expand Up @@ -306,7 +308,7 @@ export class UserDetailViewComponent extends BaseUiComponent implements OnInit,
title: [``],
first_name: [``],
last_name: [``],
gender: [``],
gender_id: [``],
email: [``, [createEmailValidator()]],
last_email_sent: [``],
default_password: [``],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { MatMenuModule } from '@angular/material/menu';
import { MatSelectModule } from '@angular/material/select';
import { MatTooltipModule } from '@angular/material/tooltip';
import { IconContainerModule } from 'src/app/ui/modules/icon-container';
import { SearchSelectorModule } from 'src/app/ui/modules/search-selector';

import { OpenSlidesTranslationModule } from '../../../site/modules/translations/openslides-translation.module';
import { PasswordFormComponent } from './components/password-form/password-form.component';
Expand Down Expand Up @@ -51,6 +52,7 @@ const MODULES = [MatInputModule, MatMenuModule];
MatFormFieldModule,
MatCardModule,
MatSelectModule,
SearchSelectorModule,
...MODULES
]
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export class ViewSpeaker extends BaseHasMeetingUserViewModel<Speaker> {
}

public get gender(): string {
return this.user ? this.user.gender : ``;
return this.user ? this.user.gender?.name : ``;
}

public get contentType(): string {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ export class ParticipantDetailViewComponent extends BaseMeetingComponent {
if (payload.member_number === ``) {
payload.member_number = null;
}
if (payload.gender_id === 0) {
payload.gender_id = null;
}
const title = _(`This action will remove you from one or more groups.`);
const content = _(
`This may diminish your ability to do things in this meeting and you may not be able to revert it by youself. Are you sure you want to do this?`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,9 @@ export class ParticipantCreateWizardComponent extends BaseMeetingComponent imple
.filter((id: Id | undefined) => !!id)
: []
};
if (payload.gender_id === 0) {
payload.gender_id = null;
}
if (this._accountId) {
this.repo
.update(payload, {
Expand Down
Loading

0 comments on commit f097ed2

Please sign in to comment.