Multiple files in Apartment

User
namespace AppModel;

use SilverStripeORMDataObject;
use SilverStripeSecurityMember;
class Apartment extends DataObject
{
private static $table_name = 'Apartment';

private static $db = [
'ObjectNumber' => 'Varchar',
'GroupNumber' => 'Varchar',
'WohnungType' => 'Varchar',
'Uberschrift' => 'HTMLText',
'Mietwohnung' => 'HTMLText',
'Dieumgebung' => 'HTMLText',
'BilderId' => 'Int',
'VideoId' => 'Int',
'DocumentId'=>'Int'
'GrundId' => 'Int',
'ContactId'=>'Int',
'MemberID' => 'Int'
];

private static $has_one = [
'Member' => Member::class
];
}
we need to store multiple file Bilder,video

Assistant
Got it. For **multiple images (“Bilder”) and videos** on an `Apartment`, use dedicated relation objects so you can sort and (optionally) add captions. Also—tiny fix—you’re missing a comma after `DocumentId` in `$db`.

Here’s a clean, production‑friendly setup.

### 1) Apartment model (owns files, sortable lists)

<?php

namespace AppModel;

use SilverStripeORMDataObject;
use SilverStripeSecurityMember;
use SilverStripeFormsFieldList;
use SilverStripeFormsGridFieldGridField;
use SilverStripeFormsGridFieldGridFieldConfig_RelationEditor;
use SilverStripeAssetsImage;
use SilverStripeAssetsFile;
use SymbioteGridFieldExtensionsGridFieldOrderableRows;

class Apartment extends DataObject
{
    private static $table_name = 'Apartment';

    private static $db = [
        'ObjectNumber' => 'Varchar',
        'GroupNumber'  => 'Varchar',
        'WohnungType'  => 'Varchar',
        'Uberschrift'  => 'HTMLText',
        'Mietwohnung'  => 'HTMLText',
        'Dieumgebung'  => 'HTMLText',
        'BilderId'     => 'Int',
        'VideoId'      => 'Int',
        'DocumentId'   => 'Int',   // <-- missing comma fixed
        'GrundId'      => 'Int',
        'ContactId'    => 'Int',
        'MemberID'     => 'Int'
    ];

    private static $has_one = [
        'Member' => Member::class
    ];

    // Multiple media via relation objects
    private static $has_many = [
        'Bilder' => ApartmentImage::class,
        'Videos' => ApartmentVideo::class,
    ];

    // Ensure related files publish with the Apartment
    private static $owns = [
        'Bilder',
        'Videos',
    ];

    public function getCMSFields()
    {
        $fields = parent::getCMSFields();

        // Images grid (sortable)
        $imgConfig = GridFieldConfig_RelationEditor::create();
        if (class_exists(GridFieldOrderableRows::class)) {
            $imgConfig->addComponent(new GridFieldOrderableRows('Sort'));
        }
        $fields->addFieldToTab(
            'Root.Bilder',
            GridField::create('Bilder', 'Bilder (Images)', $this->Bilder(), $imgConfig)
        );

        // Videos grid (sortable)
        $vidConfig = GridFieldConfig_RelationEditor::create();
        if (class_exists(GridFieldOrderableRows::class)) {
            $vidConfig->addComponent(new GridFieldOrderableRows('Sort'));
        }
        $fields->addFieldToTab(
            'Root.Videos',
            GridField::create('Videos', 'Videos', $this->Videos(), $vidConfig)
        );

        return $fields;
    }
}

### 2) Relation objects with upload fields

<?php

namespace AppModel;

use SilverStripeORMDataObject;
use SilverStripeAssetsImage;
use SilverStripeAssetsFile;
use SilverStripeFormsFieldList;
use SilverStripeAssetAdminFormsUploadField;

class ApartmentImage extends DataObject
{
    private static $table_name = 'ApartmentImage';

    private static $db = [
        'Caption' => 'Varchar(255)',
        'Sort'    => 'Int',
    ];

    private static $has_one = [
        'Apartment' => Apartment::class,
        'Image'     => Image::class,
    ];

    private static $owns = ['Image'];

    private static $summary_fields = [
        'Image.CMSThumbnail' => 'Preview',
        'Caption'            => 'Caption',
        'Sort'               => 'Sort'
    ];

    public function getCMSFields()
    {
        $fields = parent::getCMSFields();

        $upload = UploadField::create('Image', 'Bild')
            ->setAllowedFileCategories('image')          // images only
            ->setIsMultiUpload(false);

        // Optional: keep images in a nice folder
        $upload->setFolderName('apartments/images');

        $fields->replaceField('ImageID', $upload);

        return $fields;
    }
}
<?php

namespace AppModel;

use SilverStripeORMDataObject;
use SilverStripeAssetsFile;
use SilverStripeFormsFieldList;
use SilverStripeAssetAdminFormsUploadField;

class ApartmentVideo extends DataObject
{
    private static $table_name = 'ApartmentVideo';

    private static $db = [
        'Title' => 'Varchar(255)',
        'Sort'  => 'Int',
    ];

    private static $has_one = [
        'Apartment' => Apartment::class,
        'Video'     => File::class, // use File for MP4, MOV, etc.
    ];

    private static $owns = ['Video'];

    private static $summary_fields = [
        'Title' => 'Title',
        'Video.Filename' => 'File',
        'Sort'  => 'Sort'
    ];

    public function getCMSFields()
    {
        $fields = parent::getCMSFields();

        $upload = UploadField::create('Video', 'Video')
            ->setAllowedExtensions(['mp4','mov','webm','m4v','avi','mkv'])
            ->setIsMultiUpload(false);

        $upload->setFolderName('apartments/videos');

        $fields->replaceField('VideoID', $upload);

        return $fields;
    }
}

### 3) (Alternative quick setup) many_many without captions
If you *don’t* need captions/per-item fields, you can skip the relation objects:

use SilverStripeAssetsImage;
use SilverStripeAssetsFile;

private static $many_many = [
    'Bilder' => Image::class,
    'Videos' => File::class,
];

private static $owns = ['Bilder','Videos'];

Then use `UploadField::create('Bilder')` / `UploadField::create('Videos')` in `getCMSFields()`.
**Note:** sorting and captions are harder with plain `many_many`.

### 4) Template usage

<% if $Bilder %>
  <% loop $Bilder.Sort('Sort') %>
    <% if $Image %>
      <img src="$Image.ScaleWidth(1200).URL" alt="$Caption.ATT" />
      <% if $Caption %><p>$Caption</p><% end_if %>
    <% end_if %>
  <% end_loop %>
<% end_if %>

<% if $Videos %>
  <% loop $Videos.Sort('Sort') %>
    <% if $Video %>
      <a href="$Video.URL">$Title</a>
    <% end_if %>
  <% end_loop %>
<% end_if %>

### 5) Install notes
– If you want drag‑drop sorting: `composer require symbiote/silverstripe-gridfield-extensions`.
– Run: `dev/build?flush=1` after adding these classes.
– Ensure filesystem permissions allow uploads.
– For publishing: since `$owns` is set, publishing the `Apartment` will also publish attached files.

This gives you clean multi‑file support for images and videos with ordering, captions, and proper publishing behavior.

User
No I don't wantitle just want to save BilderId,VideoId,DocumentID,GrundId all is files I want to stroe file upload id here
Assistant
Got it—you just want to store a **single uploaded file ID per field** (no titles/captions), e.g. `BilderID`, `VideoID`, `DocumentID`, `GrundID`.

In SilverStripe, the correct/clean way is to use **`has_one` to File/Image** (not raw `Int`). SilverStripe will create the `…ID` columns for you and `UploadField` will Just Work.

### Model

namespace AppModel;

use SilverStripeORMDataObject;
use SilverStripeSecurityMember;
use SilverStripeAssetsFile;
use SilverStripeAssetsImage;

class Apartment extends DataObject
{
    private static $table_name = 'Apartment';

    private static $db = [
        'ObjectNumber' => 'Varchar',
        'GroupNumber'  => 'Varchar',
        'WohnungType'  => 'Varchar',
        'Uberschrift'  => 'HTMLText',
        'Mietwohnung'  => 'HTMLText',
        'Dieumgebung'  => 'HTMLText',
        // REMOVE any manual Int columns like BilderId/VideoId/etc
        'ContactId'    => 'Int',
    ];

    private static $has_one = [
        'Member'   => Member::class,
        'Bilder'   => Image::class, // creates BilderID (int) in DB
        'Video'    => File::class,  // VideoID
        'Document' => File::class,  // DocumentID
        'Grund'    => File::class,  // GrundID
    ];

    // Ensures files publish with the Apartment
    private static $owns = ['Bilder','Video','Document','Grund'];
}

### CMS fields (single upload each)

use SilverStripeFormsFieldList;
use SilverStripeAssetAdminFormsUploadField;

public function getCMSFields()
{
    $fields = parent::getCMSFields();

    $bilder = UploadField::create('Bilder', 'Bild')
        ->setAllowedFileCategories('image')
        ->setFolderName('apartments/images');

    $video = UploadField::create('Video', 'Video')
        ->setAllowedExtensions(['mp4','mov','webm','m4v','avi','mkv'])
        ->setFolderName('apartments/videos');

    $document = UploadField::create('Document', 'Dokument')
        ->setAllowedFileCategories('document')
        ->setFolderName('apartments/documents');

    $grund = UploadField::create('Grund', 'Grundriss')
        ->setAllowedExtensions(['pdf','png','jpg','jpeg','webp'])
        ->setFolderName('apartments/grundrisse');

    $fields->addFieldsToTab('Root.Main', [$bilder, $video, $document, $grund]);

    return $fields;
}

### Template usage

<% if $Bilder %><img src="$Bilder.ScaleMax(1200,800).URL" alt="" /><% end_if %>
<% if $Video %><a href="$Video.URL">$Video.Name</a><% end_if %>
<% if $Document %><a href="$Document.URL">$Document.Name</a><% end_if %>
<% if $Grund %><a href="$Grund.URL">$Grund.Name</a><% end_if %>

### Notes
– Use **`Bilder` / `Video` / `Document` / `Grund`** relations; the framework creates **`BilderID`**, **`VideoID`**, etc. You **don’t** need manual `Int` fields—remove them if present.
– Run **`/dev/build?flush=1`** after changes.
– If you ever need *multiple* files per type, switch to `has_many`/`many_many`; for now you’re set with one ID per field.

User
I will use FileField not Upllaod field in form
Assistant
Perfect 👍 If you’re working with **custom front-end forms** (not CMS), then instead of `UploadField` (CMS only), you use `FileField` so a user can pick and upload a file.

Here’s how you’d set it up in your `Apartment` model + a form:

### Model (`Apartment.php`)
Keep the `has_one` setup to `File`/`Image`, because that automatically gives you `BilderID`, `VideoID`, `DocumentID`, `GrundID` in the DB:

private static $has_one = [
    'Bilder'   => SilverStripeAssetsImage::class,
    'Video'    => SilverStripeAssetsFile::class,
    'Document' => SilverStripeAssetsFile::class,
    'Grund'    => SilverStripeAssetsFile::class,
];

### Controller form (using `FileField`)

use SilverStripeFormsForm;
use SilverStripeFormsFieldList;
use SilverStripeFormsFileField;
use SilverStripeFormsFormAction;
use SilverStripeFormsRequiredFields;
use SilverStripeAssetsUpload;

public function ApartmentForm()
{
    $fields = FieldList::create(
        FileField::create('Bilder', 'Bild'),
        FileField::create('Video', 'Video'),
        FileField::create('Document', 'Dokument'),
        FileField::create('Grund', 'Grundriss')
    );

    $actions = FieldList::create(
        FormAction::create('doSaveApartment', 'Save')
    );

    $validator = RequiredFields::create();

    return Form::create($this, 'ApartmentForm', $fields, $actions, $validator);
}

### Form handler

public function doSaveApartment($data, $form)
{
    $apartment = Apartment::create();

    $uploader = Upload::create();
    $uploader->setFolderName('apartments/uploads');

    // Save Bilder (Image)
    if (!empty($_FILES['Bilder']['name'])) {
        $file = new SilverStripeAssetsImage();
        $uploader->loadIntoFile($_FILES['Bilder'], $file);
        $apartment->BilderID = $file->ID;
    }

    // Save Video (File)
    if (!empty($_FILES['Video']['name'])) {
        $file = new SilverStripeAssetsFile();
        $uploader->loadIntoFile($_FILES['Video'], $file);
        $apartment->VideoID = $file->ID;
    }

    // Save Document (File)
    if (!empty($_FILES['Document']['name'])) {
        $file = new SilverStripeAssetsFile();
        $uploader->loadIntoFile($_FILES['Document'], $file);
        $apartment->DocumentID = $file->ID;
    }

    // Save Grundriss (File)
    if (!empty($_FILES['Grund']['name'])) {
        $file = new SilverStripeAssetsFile();
        $uploader->loadIntoFile($_FILES['Grund'], $file);
        $apartment->GrundID = $file->ID;
    }

    $apartment->write();

    $form->sessionMessage('Apartment saved successfully', 'good');
    return $this->redirectBack();
}

✅ This way:
– Your database gets **BilderID, VideoID, DocumentID, GrundID** filled with file IDs.
– You’re using **`FileField`** in the form (frontend-friendly).
– Uploading is handled via **`Upload::loadIntoFile`**, which moves the file into the assets store and attaches it.

Do you want me to also show you how to **display those uploaded files** back in the template (frontend)?

User
Now I want a other table depende Aprtent ApartmentAddress and Aprtment will have one address
Assistant
Great—let’s add a separate **ApartmentAddress** table and link **one Address per Apartment**.

### 1) Models

**Apartment.php** (add a `has_one` to the address)

namespace AppModel;

use SilverStripeORMDataObject;
use SilverStripeSecurityMember;
use SilverStripeAssetsFile;
use SilverStripeAssetsImage;

class Apartment extends DataObject
{
    private static $table_name = 'Apartment';

    private static $db = [
        'ObjectNumber' => 'Varchar',
        'GroupNumber'  => 'Varchar',
        'WohnungType'  => 'Varchar',
        'Uberschrift'  => 'HTMLText',
        'Mietwohnung'  => 'HTMLText',
        'Dieumgebung'  => 'HTMLText',
        'ContactId'    => 'Int',
    ];

    private static $has_one = [
        'Member'   => Member::class,
        // Files (each stores the *_ID automatically)
        'Bilder'   => Image::class,
        'Video'    => File::class,
        'Document' => File::class,
        'Grund'    => File::class,

        // One address per apartment
        'Address'  => ApartmentAddress::class,
    ];

    private static $owns = ['Bilder','Video','Document','Grund','Address'];
}

**ApartmentAddress.php**

namespace AppModel;

use SilverStripeORMDataObject;

class ApartmentAddress extends DataObject
{
    private static $table_name = 'ApartmentAddress';

    private static $db = [
        'Street'    => 'Varchar(255)',
        'HouseNo'   => 'Varchar(50)',
        'City'      => 'Varchar(100)',
        'State'     => 'Varchar(100)',
        'Postcode'  => 'Varchar(20)',
        'Country'   => 'Varchar(100)',
        // Optional geo fields:
        'Latitude'  => 'Decimal(10,6)',
        'Longitude' => 'Decimal(10,6)',
    ];

    // Inverse pointer (no extra column; ties to Apartment.Address)
    private static $belongs_to = [
        'Apartment' => Apartment::class . '.Address',
    ];

    private static $summary_fields = [
        'FullAddress' => 'Address'
    ];

    public function getFullAddress(): string
    {
        $parts = array_filter([
            "{$this->Street} {$this->HouseNo}",
            $this->City,
            $this->State,
            $this->Postcode,
            $this->Country
        ]);
        return implode(', ', $parts);
    }
}

> Run `/dev/build?flush=1` after adding these.

### 2) Front-end form (you said you’ll use `FileField`)
Add address fields to your form and save them, then attach to the apartment:

use SilverStripeFormsForm;
use SilverStripeFormsFieldList;
use SilverStripeFormsTextField;
use SilverStripeFormsFileField;
use SilverStripeFormsFormAction;
use SilverStripeFormsRequiredFields;
use SilverStripeAssetsUpload;
use AppModelApartment;
use AppModelApartmentAddress;

public function ApartmentForm()
{
    $fields = FieldList::create(
        // Files
        FileField::create('Bilder', 'Bild'),
        FileField::create('Video', 'Video'),
        FileField::create('Document', 'Dokument'),
        FileField::create('Grund', 'Grundriss'),

        // Address
        TextField::create('Street', 'Street'),
        TextField::create('HouseNo', 'House No.'),
        TextField::create('City', 'City'),
        TextField::create('State', 'State'),
        TextField::create('Postcode', 'Postcode'),
        TextField::create('Country', 'Country')
    );

    $actions = FieldList::create(
        FormAction::create('doSaveApartment', 'Save')
    );

    return Form::create($this, 'ApartmentForm', $fields, $actions, RequiredFields::create());
}

public function doSaveApartment($data, $form)
{
    $apartment = Apartment::create();

    // Handle file uploads
    $uploader = Upload::create()->setFolderName('apartments/uploads');

    foreach (['Bilder' => SilverStripeAssetsImage::class,
              'Video'  => SilverStripeAssetsFile::class,
              'Document' => SilverStripeAssetsFile::class,
              'Grund'    => SilverStripeAssetsFile::class] as $field => $class) {

        if (!empty($_FILES[$field]['name'])) {
            $file = new $class();
            $uploader->loadIntoFile($_FILES[$field], $file);
            $apartment->{$field . 'ID'} = $file->ID; // e.g. BilderID, VideoID...
        }
    }

    // Create & attach address
    $addr = ApartmentAddress::create([
        'Street'   => $data['Street']   ?? '',
        'HouseNo'  => $data['HouseNo']  ?? '',
        'City'     => $data['City']     ?? '',
        'State'    => $data['State']    ?? '',
        'Postcode' => $data['Postcode'] ?? '',
        'Country'  => $data['Country']  ?? '',
    ]);
    $addr->write();

    $apartment->AddressID = $addr->ID;
    $apartment->write();

    $form->sessionMessage('Apartment saved with address.', 'good');
    return $this->redirectBack();
}

### 3) Template usage

<% if $Address %>
  <p>$Address.FullAddress</p>
<% end_if %>

This gives you a clean 1‑to‑1 **Apartment → Address** link, stores address in its own table, and keeps your file IDs exactly as you wanted (via `has_one` to File/Image).

User
Same for ApartmentDetails
Assistant
Perfect—let’s add a **one‑to‑one** details table: **Apartment → ApartmentDetails**.

### 1) Model: `ApartmentDetails.php`

namespace AppModel;

use SilverStripeORMDataObject;

class ApartmentDetails extends DataObject
{
    private static $table_name = 'ApartmentDetails';

    private static $db = [
        'AreaSqm'     => 'Decimal(10,2)', // e.g. 82.50
        'Rooms'       => 'Int',           // e.g. 3
        'Bathrooms'   => 'Int',
        'Floor'       => 'Varchar(20)',   // e.g. 2nd / EG
        'HeatingType' => 'Varchar(100)',
        'RentCold'    => 'Decimal(12,2)', // basic rent
        'RentWarm'    => 'Decimal(12,2)', // incl. utilities
        'Deposit'     => 'Decimal(12,2)',
        'AvailableFrom'=> 'Date',
        'Description' => 'HTMLText',
    ];

    // Inverse of Apartment.Has one Details
    private static $belongs_to = [
        'Apartment' => Apartment::class . '.Details',
    ];

    private static $summary_fields = [
        'AreaSqm'   => 'm²',
        'Rooms'     => 'Rooms',
        'RentCold'  => 'Cold Rent',
        'AvailableFrom.Nice' => 'Available',
    ];
}

### 2) Update `Apartment.php` (add relation)

private static $has_one = [
    'Member'   => SilverStripeSecurityMember::class,
    'Bilder'   => SilverStripeAssetsImage::class,
    'Video'    => SilverStripeAssetsFile::class,
    'Document' => SilverStripeAssetsFile::class,
    'Grund'    => SilverStripeAssetsFile::class,

    // One details row per apartment
    'Details'  => AppModelApartmentDetails::class,
];

private static $owns = ['Bilder','Video','Document','Grund','Details'];

> Run `/dev/build?flush=1` after adding the class & relation.

### 3) Front‑end form fields (you’re using `FileField`)
Add simple detail inputs alongside your existing file fields:

use SilverStripeFormsTextField;
use SilverStripeFormsNumericField;
use SilverStripeFormsTextareaField;
use SilverStripeFormsDateField;

public function ApartmentForm()
{
    $fields = FieldList::create(
        // existing FileFields:
        FileField::create('Bilder', 'Bild'),
        FileField::create('Video', 'Video'),
        FileField::create('Document', 'Dokument'),
        FileField::create('Grund', 'Grundriss'),

        // Details:
        NumericField::create('AreaSqm', 'Area (m²)'),
        NumericField::create('Rooms', 'Rooms'),
        NumericField::create('Bathrooms', 'Bathrooms'),
        TextField::create('Floor', 'Floor'),
        TextField::create('HeatingType', 'Heating Type'),
        NumericField::create('RentCold', 'Cold Rent'),
        NumericField::create('RentWarm', 'Warm Rent'),
        NumericField::create('Deposit', 'Deposit'),
        DateField::create('AvailableFrom', 'Available From')->setHTML5(true),
        TextareaField::create('Description', 'Description')
    );

    $actions = FieldList::create(FormAction::create('doSaveApartment', 'Save'));
    return Form::create($this, 'ApartmentForm', $fields, $actions);
}

### 4) Save handler (create `ApartmentDetails`, attach to `Apartment`)
Add this to your existing `doSaveApartment` handler after file‑upload logic:

use AppModelApartmentDetails;

public function doSaveApartment($data, $form)
{
    $apartment = AppModelApartment::create();

    // ... your existing FileField upload code that sets BilderID, VideoID, DocumentID, GrundID ...

    // Create & attach details
    $details = ApartmentDetails::create([
        'AreaSqm'      => $data['AreaSqm']      ?? null,
        'Rooms'        => $data['Rooms']        ?? null,
        'Bathrooms'    => $data['Bathrooms']    ?? null,
        'Floor'        => $data['Floor']        ?? '',
        'HeatingType'  => $data['HeatingType']  ?? '',
        'RentCold'     => $data['RentCold']     ?? null,
        'RentWarm'     => $data['RentWarm']     ?? null,
        'Deposit'      => $data['Deposit']      ?? null,
        'AvailableFrom'=> $data['AvailableFrom']?? null,
        'Description'  => $data['Description']  ?? '',
    ]);
    $details->write();

    $apartment->DetailsID = $details->ID;
    $apartment->write();

    $form->sessionMessage('Apartment saved with details.', 'good');
    return $this->redirectBack();
}

### 5) Template usage

<% if $Details %>
  <ul class="apartment-details">
    <% if $Details.AreaSqm %><li>Area: $Details.AreaSqm m²</li><% end_if %>
    <% if $Details.Rooms %><li>Rooms: $Details.Rooms</li><% end_if %>
    <% if $Details.Bathrooms %><li>Bathrooms: $Details.Bathrooms</li><% end_if %>
    <% if $Details.Floor %><li>Floor: $Details.Floor</li><% end_if %>
    <% if $Details.HeatingType %><li>Heating: $Details.HeatingType</li><% end_if %>
    <% if $Details.RentCold %><li>Cold Rent: $Details.RentCold</li><% end_if %>
    <% if $Details.RentWarm %><li>Warm Rent: $Details.RentWarm</li><% end_if %>
    <% if $Details.Deposit %><li>Deposit: $Details.Deposit</li><% end_if %>
    <% if $Details.AvailableFrom %><li>Available: $Details.AvailableFrom.Nice</li><% end_if %>
  </ul>
  <% if $Details.Description %><div class="desc">$Details.Description</div><% end_if %>
<% end_if %>

That’s it—**`ApartmentDetails`** is now a clean one‑to‑one companion to **`Apartment`**, saved from the same front‑end form, and publish‑linked via `$owns`.

Leave a Comment

Your email address will not be published. Required fields are marked *